Мне было поручено расширить и изменить плагин Shopware. Оригинальный автор больше не в компании. До этого я никогда не имел дело с Shopware и ExtJs.
Поэтому я провожу последние пару дней, чтобы понять себя, и думаю, что до сих пор понял принципы и парадигму.
Единственное, с чем у меня сейчас проблемы, это следующая проблема:
У меня есть Ext.tree.Panel
который я хочу сохранить в базе данных с помощью Ajax. Узел добавляется в дерево, я вижу его в графическом интерфейсе. Но после звонка optionsTree.getStore().sync()
в базе данных ничего не поступает. createProductOptionAction()
в PHP контроллер не вызывается, но я не могу понять, почему. В журнале консоли браузера нет сообщений об ошибках, в файлах журналов Shopware нет сообщений об ошибках. Ничего такого. Кажется, все работает нормально. Но данные не хранятся.
Оригинальный плагин имел Ext.grid.Panel
хранить и отображать данные. И это прекрасно работает. Но после изменения на Ext.tree.Panel
и изменение кода, он больше не работает. С моей точки зрения это должно работать. Но это не так, и я не вижу своих ошибок.
Любая помощь действительно ценится, я все еще чертовски новичок в ExtJs. 🙂
Вот что у меня так далеко:
app.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
Я попытался добавить писателя для магазина и модели, как это было предложено Саки. Но, к сожалению, это все еще не работает. 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]
...
Но все же запросы API model.ProductOption
не увольняют. Он должен работать. Не должно ли это? Может быть, это ошибка в ExtJS 4.1? Или что-то с Shopware + ExtJS?
РЕДАКТИРОВАТЬ 3
Хорошо, это действительно странно.
Я добавил «write» -Listener к 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();
}
});
}
После добавления узла и вызова .getStore().sync()
, write
— событие уволено, он повторяется operation.records
, находит одну запись (ту, которую я только что добавил) … но это не так dirty
хотя я делаю productOption.setDirty()
прежде чем добавить его в дерево ?!
Большое спасибо за ваше время! 🙂
Небольшая заметка о вашем коде:
Ошибка в 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'}
]
});
Я не разработал серверный код. Я просто скопировал Пример кода кухонной раковины. Чтобы заставить работать запрос на создание, обновление или удаление, он должен вернуть измененные строки вместе с success: true
,
Пояснения:
sencha app build
после добавления необходимых классов, чтобы все отображалось правильноmodel/TreeTest.js
: поле index
требуется, если мы хотим, чтобы повторный заказ был сохранен на сервере. Если он пропущен, только строки с отредактированными полями сохраняются обратно. Надо было добавить persist: false
для leaf
поле, потому что эти данные не нужны на сервере.файл store/TreeTest.js
:
autoSync: true
работал из коробки, с ограничением, упомянутым при переупорядочении. expanded: true
или если autoLoad: true
, Если мы не хотим автозагрузку, autoLoad
а также expanded
должны быть оба ложными.autoLoad: true
,api
конфигурации. Без этого невозможно было сообщить appart обновление, создать и удалить запрос.файл view/TreeGrid.js
:
xtype: 'treecolumn'
необходимо.sync()
как только appendChild()
называется (suspendAutoSync()
используется, чтобы избежать написания нового потомка до его редактирования). Кроме того, сетка обновляется только, если мы контролируем leaf
свойство вручную (.set('leaf', false)
). Я ожидаю, что ExtJs правильно управлять leaf
свойство и считают это ошибкой.Используемый магазином прокси должен иметь писатель настроен для синхронизации операций для общения с сервером.