0

我被分配了一项任务来扩展和修改 Shopware 插件。原作者不在公司。在此之前,我从未处理过 Shopware 和 ExtJs。所以我花了最后几天让自己投入其中,我认为到目前为止我理解了这些原则和范式。

我现在唯一遇到的问题是以下问题:

我有一个Ext.tree.Panel我想使用 Ajax 保存到数据库中的内容。该节点正在添加到树中,我可以看到它出现在 GUI 中。但是在调用之后optionsTree.getStore().sync()没有任何东西到达数据库。没有调用 PHP 控制器中的createProductOptionAction(),但我不知道为什么。浏览器控制台日志中没有错误消息,Shopware 日志文件中没有错误消息。没有。一切似乎都很好。但是数据没有被存储。

原始插件具有Ext.grid.Panel存储和显示数据的功能。这很好用。但是在更改Ext.tree.Panel和修改代码之后,它就不再起作用了。从我的角度来看,它应该可以工作。但它没有,我看不到我的错误。

任何帮助都非常感谢,我仍然是 ExtJs 的血腥初学者。:)

这是我到目前为止所得到的:

应用程序.js

Ext.define('Shopware.apps.CCBConfigurablePhotoProductsManager', {

    extend:'Enlight.app.SubApplication',
    name:'Shopware.apps.CCBConfigurablePhotoProductsManager',

    bulkLoad: true,
    loadPath:'{url controller="CCBConfigurablePhotoProductsManager" action="load"}',

    controllers:['ProductConfigurator'],
    stores:['ProductOptionsList'],
    models:['ProductOption'],
    views: ['ProductOptions', 'Window' ],

    launch: function() {
        var me = this,
            mainController = me.getController('ProductConfigurator');
        return mainController.mainWindow;
    }
});

控制器/controller.js

Ext.define('Shopware.apps.CCBConfigurablePhotoProductsManager.controller.ProductConfigurator', {

    extend:'Ext.app.Controller',

    refs: [
        { ref: 'productOptionsTree', selector: 'product-configurator-settings-window product-options-tree' }
    ],

    init:function () {
        var me = this;

        me.mainWindow = me.createMainWindow();
        me.addControls();
        me.callParent(arguments);

        return me.mainWindow;
    },

    addControls: function() {
        var me = this;

        me.control({
            'product-configurator-settings-window product-options-tree': {
                addProductOption: me.onAddProductOption
            }
        });
    },

    createMainWindow: function() {
        var me = this,
            window = me.getView('Window').create({
                treeStore: Ext.create('Shopware.apps.CCBConfigurablePhotoProductsManager.store.ProductOptionsList').load()
            }).show();

        return window;
    },

    onAddProductOption: function() {
        var me = this,
            optionsTree = me.getProductOptionsTree(),
            parentNode = optionsTree.getRootNode(),
            nodeCount = parentNode.childNodes.length + 1,
            productOption = Ext.create('Shopware.apps.CCBConfigurablePhotoProductsManager.model.ProductOption', {
                parent: 0,
                type: 0,
                title: Ext.String.format('{s name="group/default_name"}New Group [0]{/s}', optionsTree.getRootNode().childNodes.length + 1),
                active: true,
                leaf: false
            });
        productOption.setDirty();
        parentNode.appendChild(productOption);
        optionsTree.getStore().sync(); // Nothing arrives at DB
        optionsTree.expandAll();
    },

    // ...

查看/window.js

Ext.define('Shopware.apps.CCBConfigurablePhotoProductsManager.view.Window', {

    extend:'Enlight.app.Window',

    cls:Ext.baseCSSPrefix + 'product-configurator-settings-window',
    alias:'widget.product-configurator-settings-window',

    border:false,
    autoShow:true,
    maximizable:true,
    minimizable:true,

    layout: {
        type: 'hbox',
        align: 'stretch'
    },

    width: 700,
    height: 400,

    initComponent:function () {
        var me = this;
        me.createItems();
        me.title = '{s name=window/title}Configurator Settings{/s}';
        me.callParent(arguments);
    },

    createItems: function() {
        var me = this;

        me.items = [
            me.createProductOptionsTree()
        ];
    },

    createProductOptionsTree: function() {
        var me = this;
        return Ext.create('Shopware.apps.CCBConfigurablePhotoProductsManager.view.ProductOptions', {
            store: me.treeStore,
            width: '20%',
            flex: 1
        });
    }
});

商店/product_options_list.js

Ext.define('Shopware.apps.CCBConfigurablePhotoProductsManager.store.ProductOptionsList', {
    extend: 'Ext.data.TreeStore',
    pageSize: 30,
    autoLoad: false,
    remoteSort: true,
    remoteFilter: true,
    model : 'Shopware.apps.CCBConfigurablePhotoProductsManager.model.ProductOption',
    proxy:{
        type:'ajax',
        url:'{url controller="CCBConfigurablePhotoProductsManager" action="getProductOptionsList"}',
        reader:{
            type:'json',
            root:'data',
            totalProperty:'total'
        }
    }
});

模型/product_option.js

Ext.define('Shopware.apps.CCBConfigurablePhotoProductsManager.model.ProductOption', {
    extend : 'Ext.data.Model',
    fields : [
        { name : 'id', type : 'int', useNull: true },
        { name : 'parent', type : 'int' },
        { name : 'title', type : 'string' },
        { name : 'active', type: 'boolean' },
        { name : 'type', type : 'int' }
    ],
    idProperty : 'id',
    proxy : {
        type : 'ajax',
        api: {
            create: '{url controller="CCBConfigurablePhotoProductsManager" action="createProductOption"}',
            update: '{url controller="CCBConfigurablePhotoProductsManager" action="updateProductOption"}',
            destroy: '{url controller="CCBConfigurablePhotoProductsManager" action="deleteProductOption"}'
        },
        reader : {
            type : 'json',
            root : 'data',
            totalProperty: 'total'
        }
    }
});

php/controller.php

<?php
use Shopware\CustomModels\CCB\ProductOption;

class Shopware_Controllers_Backend_CCBConfigurablePhotoProductsManager extends Shopware_Controllers_Backend_ExtJs
{

    public function createProductOptionAction()
    {
        // Never being called
        file_put_contents('~/test.log', "createProductOptionAction\n", FILE_APPEND);
        $this->View()->assign(
            $this->saveProductOption($this->Request()->getParams())
        );
    }

    public function getProductOptionsListAction()
    {
        // Works fine
        file_put_contents('~/test.log', "getProductOptionsListAction\n", FILE_APPEND);
        // ...
    }

    // ...

编辑 1

根据 Saki 的建议,我尝试为商店和模型添加一个作家。但不幸的是,它仍然不起作用。createProductOptionAction()PHP 控制器中的 永远不会被调用。

Ext.define('Shopware.apps.CCBConfigurablePhotoProductsManager.model.ProductOption', {
    extend : 'Ext.data.Model',
    fields : [
        { name : 'id', type : 'int', useNull: true },
        { name : 'parent', type : 'int' },
        { name : 'title', type : 'string' },
        { name : 'active', type: 'boolean' },
        { name : 'type', type : 'int' }
    ],
    idProperty : 'id',
    proxy : {
        type: 'ajax',
        api: {
            create: '{url controller="CCBConfigurablePhotoProductsManager" action="createProductOption"}',
            update: '{url controller="CCBConfigurablePhotoProductsManager" action="updateProductOption"}',
            destroy: '{url controller="CCBConfigurablePhotoProductsManager" action="deleteProductOption"}'
        },
        reader: {
            type : 'json',
            root : 'data',
            totalProperty: 'total'
        },
        writer: {
            type: 'json'
        }
    }
});

我想知道的是,原始插件没有实现编写器。但是当添加一个条目时,它立即出现在数据库中。

编辑 2

我添加了几个监听器store.ProductOptionsList

Ext.define('Shopware.apps.CCBConfigurablePhotoProductsManager.store.ProductOptionsList', {
    extend: 'Ext.data.TreeStore',
    pageSize: 30,
    autoLoad: false,
    remoteSort: true,
    remoteFilter: true,
    model : 'Shopware.apps.CCBConfigurablePhotoProductsManager.model.ProductOption',
    root: {
        text: 'Product Options',
        id: 'productOptions',
        expanded: true
    },
    proxy:{
        type: 'ajax',
        url: '{url controller="CCBConfigurablePhotoProductsManager" action="getProductOptionsList"}',
        reader: {
            type:'json',
            root:'data',
            totalProperty:'total'
        }
    },
    listeners: {
        add: function(store, records, index, eOpts) {
            console.log("**** Add fired");
            console.log(records);
        },
        append: function(store, node, index, eOpts) {
            console.log("**** Append fired");
            console.log(node);
        },
        beforesync: function(operations) {
            console.log("**** Beforesync fired");
            console.log(operations);
        }
    }
});

所有这些事件都被解雇了。beforesync活动显示

**** Beforesync fired
Object {create: Array[1]}
  create: Array[1]
  ...

但是,仍然model.ProductOption没有触发 API 请求。它应该工作。不应该吗?也许这是 ExtJS 4.1 中的一个错误?或者 Shopware + ExtJS 的东西?

编辑 3

好吧,这真的越来越奇怪了。

我在TreeStore.

    write: function(store, operation, opts){
        console.log("**** Write fired");
        console.log(operation);
        Ext.each(operation.records, function(record){
            console.log("**** ...");
            if (record.dirty) {
                console.log("**** Commiting dirty record");
                record.commit();
            }
        });
    }

添加一个 Node 并调用之后.getStore().sync(),触发了write-event,他进行了迭代operation.records,找到了一条记录(我刚刚添加的那条)......但它不是dirty,即使我productOption.setDirty()在将它添加到 Tree 之前做了?!

非常感谢您的时间!:)

4

2 回答 2

1

关于您的代码的一点说明:beforesync事件中有一个错误:函数必须返回true,否则sync()不会被触发。

我不认为这是唯一的问题。由于 ExtJs 通常是大量代码,因此我无法告诉您问题的原因是什么。我所能提供的只是一个简单的工作示例和一些解释。

我遵循推荐的 MVC 布局,即每个类一个文件。这是完整的代码:

Ext.define('Sandbox.Application', {
    name: 'Sandbox',
    extend: 'Ext.app.Application',
    controllers: [
        'Sandbox.controller.Trees'
    ]
});
Ext.define('Sandbox.controller.Trees', {
    extend: 'Ext.app.Controller',
    requires: ['Ext.tree.*', 'Ext.data.*', 'Ext.grid.*'],
    models: ['TreeTest'],
    stores: ['TreeTest'],
    views: ['TreeGrid'],
    init: function(){
        this.control({
            'treegrid toolbar button#addchild': {click: this.onAddChild},
            'treegrid toolbar button#removenode': {click: this.onRemoveNode}
        })
    },
    onAddChild: function(el){
        var grid = el.up('treepanel'),
        sel = grid.getSelectionModel().getSelection()[0],
        store = grid.getStore();
        store.suspendAutoSync()
        var child = sel.appendChild({task: '', user: '', leaf: true});
        sel.set('leaf', false)
        sel.expand()
        grid.getView().editingPlugin.startEdit(child);
        store.resumeAutoSync();
    },
    onRemoveNode: function(el){
        var grid = el.up('treepanel'),
        sel = grid.getSelectionModel().getSelection()[0];
        sel.remove()
    }
});
Ext.define('Sandbox.model.TreeTest', {
    extend: 'Ext.data.TreeModel',
    fields: [
        {name: 'id',  type: 'int'},
        {name: 'task',  type: 'string'},
        {name: 'user', type: 'string'},
        {name: 'index', type: 'int'},
        {name: 'parentId', type: 'int'},
        {name: 'leaf', type: 'boolean', persist: false}
    ]
});
Ext.define('Sandbox.store.TreeTest', {
    extend: 'Ext.data.TreeStore',
    model: 'Sandbox.model.TreeTest',
    proxy: {
        type: 'ajax',
        url: 'resources/treedata.php',
        api: {
            create: 'resources/treedata.php?action=create',
            read: undefined,
            update: 'resources/treedata.php?action=update',
            destroy: 'resources/treedata.php?action=destroy'
        }
    },
    autoSync: true,
    autoLoad: false,
    root: {id: 1, text: "Root Node", expanded: false}
});
Ext.define('Sandbox.view.TreeGrid', {
    extend: 'Ext.tree.Panel',
    alias: 'widget.treegrid',
    store: 'TreeTest',
    columns: [{
        xtype: 'treecolumn',
        text: 'Task',
        flex: 2,
        sortable: true,
        dataIndex: 'task',
        editor: {xtype: 'textfield', allowBlank: false}
    },{
        dataIndex: 'id',
        align: 'right',
        text: 'Id'
    }, {
        dataIndex: 'user',
        flex: 1,
        text: 'Utilisateur',
        editor: {xtype: 'textfield', allowBlank: false}
    }],
    plugins: [{
        ptype: 'rowediting',
        clicksToMoveEditor: 1,
        autoCancel: false
    }],
    viewConfig: {
        plugins: [{
            ptype: 'treeviewdragdrop',
            containerScroll: true
        }]
    },
    tbar:[
        {xtype: 'button', text: 'Add Child', itemId: 'addchild'},
        {xtype: 'button', text: 'Remove', itemId: 'removenode'}
    ]
});

我没有详细说明服务器端代码。我刚刚复制了Kitchensink 示例代码。要开始创建、更新或删除请求,它必须返回修改后的行以及success: true.

说明:

  • 我需要sencha app build在添加所需的类后启动才能正确显示所有内容
  • file :如果我们想要将重新排序保存回服务器,则model/TreeTest.js该字段是必需的。index如果省略,则仅保存带有已编辑字段的行。必须persist: false为该leaf字段添加,因为服务器上不需要此数据。
  • 文件store/TreeTest.js

    • autoSync: true开箱即用,并提到了重新排序的限制。
    • 当根节点为expanded: true或 if时,树自动加载autoLoad: true。如果我们不想自动加载,则autoLoad必须expanded两者都为假。
    • 良好的工作商店需要root。如果它丢失,我们必须手动加载商店,即使autoLoad: true.
    • 有必要添加api配置。没有它,就不可能告诉 appart 更新、创建和删除请求。
  • 文件view/TreeGrid.js

    • 需要一列xtype: 'treecolumn'
    • 删除一行很简单,并且会自动同步存储。如果有子节点,服务器端负责删除子节点。
    • 新行的创建比较棘手,因为sync()一旦appendChild()调用 store 就会被 'd (suspendAutoSync()用于避免在编辑新子项之前写入它)。leaf此外,如果我们手动控制属性(.set('leaf', false)),网格只会更新。我希望 ExtJs 能够正确管理该leaf属性并将其视为一个错误。
于 2015-02-10T16:39:51.090 回答
0

存储使用的代理必须有一个为同步操作配置的写入器才能与服务器通信。

于 2015-02-05T12:42:47.627 回答