I have a grid populating records from view model (Ajax proxy data from Java restful web services). When I select a record in the grid form open and the fields are displayed. The records are editable using the form.
I have one tag field which is binded to a store and I should be able to populate the data associated to the record whenever the user selects a record.
"data" : [ {
"createdOn" : 1475678859000,
"updatedOn" : 1475679885000,
"updatedBy" : null,
"id" : 174,
"userName" : "ffff,
"firstName" : "gg",
"lastName" : "ggg",
"salesDepartment" : [ {
"id" : 3,
"code" : "FC",
"name" : "Fiat-Chrysler",
"departmentHead" : "xxx",
"applicationCode" : null} ],}
I need to bind the value of id from sales department object . how can I achieve this. Please help me.
{xtype: 'tagfield',
anchor: '100%',
reference: 'salesDept',
fieldLabel: 'Sales Dept\'s',
name: 'salesDepartment',
allowBlank: false,
displayField: 'name',
valueField: 'id',
bind: {
value: '{record.salesDepartmentIds}',
store: '{SalesDepartmentStore}'},
}
Tag fields are really only designed to work with arrays or comma separated lists;
It looks like you are trying to use with associations, which it would be nice if there was more support for this out of the box.
I've had a go, and in terms of displaying got it working however saving values will really depend on how you plan to sync values to the server, if you are going to use in built proxies etc. then this will present a whole different challenge. I would like a similar component for my own apps, I will think on this further and may even create a UX for this.
I think you have several issues here, one is getting your associations working properly, then you need to make your tagfield work as expected.
Here is the fiddle
And the key parts are:
An extended tagfield:
Ext.define('RelatedTagField', {
extend: 'Ext.form.field.Tag',
xtype: 'rtagfield',
config: {
relatedStore: null
},
setValue: function (val) {
this.setRelatedStore(val);
var value;
if (val && val.isStore) {
value = val.collect('id');
} else {
value = '';
}
this.callParent([value]);
},
getValue: function () {
return this.relatedStore;
},
onBindStore: function () {
this.callParent(arguments);
this.on('select', function (tagfield, selection) {
var selectedIds = [];
//add any new selections
Ext.each(selection, function (rec) {
var rrec = this.relatedStore.getById(rec.id);
if (!rrec) {
this.relatedStore.add(rec.data)
}
selectedIds.push(rec.id);
}, this);
//remove any not selected anymore
this.relatedStore.each(function (rrec) {
if (selectedIds.indexOf(rrec.id) == -1) {
this.relatedStore.remove(rrec);
}
}, this);
}, this);
},
})
Binding the value:
{
xtype: 'rtagfield',
fieldLabel: 'Sales Department',
name: 'id',
displayField: 'name',
valueField: 'id',
bind: {
store: '{salesDepartment}',
value: '{record.salesDepartment}'
}
},
And adding a definition for the association:
Ext.define('MyApp.model.User', {
extend: 'Ext.data.Model',
alias: 'model.user',
hasMany: [{
model: 'MyApp.model.SalesDepartmentModel',
associationKey: 'salesDepartment',
role: 'salesDepartment'
}],
requires: [
'Ext.data.field.String'
],
fields: [{
type: 'string',
name: 'userName'
}, {
type: 'string',
name: 'firstName'
}, {
type: 'string',
name: 'lastName'
}, {
name: 'salesDepartment'
}, {
name: 'roles'
}, {
type: 'string',
name: 'email'
}, {
name: 'notificationFrequencyId'
}]
});
Further reading
I suggest you read more on associations and the tagfield source code
In terms of saving records back to the server, I would recommend looking at sessions
Related
I'm stuck in the development of an extension for Shopware.
I want to extend the administration of categories in the correct way.
To achieve that created plugin (legacy). This plugins appends the first tab within a category.
//{block name="backend/category/view/tabs/settings" append}
This is done to add a fieldset with a dropdown. The file looks like this:
//{block name="backend/category/view/tabs/settings" append}
Ext.define('Shopware.apps.HaendlerbundFoobarCategories.view.category.tabs.settings', {
override:'Shopware.apps.Category.view.category.tabs.Settings',
getItems: function() {
var me = this;
var items = me.callParent(arguments);
me.FooBar = me.getFooBarSection();
items.push(me.FooBar);
return items;
},
getFooBarSection : function()
{
var me = this;
return Ext.create('Ext.form.FieldSet',{
title: 'FooBar Verlinkung',
anchor: '100%',
defaults : me.defaults,
disabled : false,
items : me.getDropdowns()
});
},
getDropdowns:function(){
var me = this;
return me.templateComboBox = Ext.create('Ext.form.field.ComboBox', {
xtype:'combobox',
fieldLabel: 'FooBar Product',
store: me.fooBarProducts.load(),
labelWidth: 155,
valueField: 'id',
displayField:'title',
editable: true,
allowBlank:true,
name:'fbproduct'
});
}
});
//{/block}
The main problem I suspect is the store. Leaving it like this I get an JS
error Cannot read property 'load' of undefined. without the .load() there is no error, but I also can't determine if the store got loaded.
The store file itself is in Views/backend/haendlerbund_foobar_categories/store/foo_bar_products
and the content of the file is:
//{block name="backend/haendlerbund_foobar_categories/store/fooBarProducts"}
Ext.define('Shopware.apps.HaendlerbundFoobarCategories.store.fooBarProducts', {
//...
model: 'Shopware.apps.HaendlerbundFoobarCategories.model.fooBarProducts',
proxy : {
type : 'ajax',
/**
* Configure the url mapping for the different
* store operations based on
* #object
*/
api : {
read : '{url controller=HaendlerbundFoobarCategories action=getProducts}'
},
/**
* Configure the data reader
* #object
*/
reader : {
type : 'json',
root: 'data'
}
}
});
//{/block}
EDIT:
For further context, this is the current state of the model the store references.
Ext.define('Shopware.apps.HaendlerbundFoobarCategories.model.fooBarProducts', {
extend: 'Ext.data.Model',
/**
* If the name of the field is 'id' extjs assumes automatically that
* this field is an unique identifier.
* #integer
*/
idProperty : 'id',
fields:[
{ name : 'id', type: 'int' },
{ name : 'ffid', type: 'bigint' },
{ name : 'title', type: 'string' },
{ name : 'description', type: 'string' },
{ name : 'price', type: 'decimal' },
{ name : 'vat', type: 'decimal' },
{ name : 'image', type: 'text' },
{ name : 'active', type: 'boolean' },
{ name : 'createdAt', type: 'datetime' },
{ name : 'modfiedAt', type: 'datetime' }
],
/*associations: [
{
relation: 'OneToMany',
storeClass: 'Shopware.apps.HaendlerbundFoobarCategories.store.fooBarProducts',
loadOnDemand: true,
type: 'hasMany',
model: 'Shopware.apps.HaendlerbundFooBarCategories.model.fooBarProducts',
name: 'getCategories',
associationKey: 'categories'
},
]*/
});
And the php controller which the store references as well has the following content:
<?php
class Shopware_Controllers_Backend_HaendlerbundFoobarCategories extends Shopware_Controllers_Backend_Application
{
protected $model = 'Shopware\CustomModels\Product\FFProduct';
protected $alias = 'ffproducts';
protected function getListQuery()
{
$builder = parent::getListQuery();
return $builder;
}
protected function getDetailQuery($id)
{
$builder = parent::getDetailQuery($id);
return $builder;
}
protected function getAdditionalDetailData(array $data)
{
return $data;
}
public function getProducts(){
$builder = $this->getManager()->createQueryBuilder();
$builder->select('*')
->from($model, $alias);
$data['id'] = 1;
$data['ffid'] = 1;
$data['title'] = 'lorem ipsum';
$this->view()->assign([
'success' => true,
'data' => $data,
'total' => 1
]);
}
}
It should return some dummy data for now.
I fail to solve the problem. As any documentation I was able to find was either focused on creating a separate component of changing an existing field. I suspect that I'm in the wrong scope or have a namespacing error or something.
Any help is greatly appreciated.
Your problem is the store assigning to combobox
this line of code isn't correct:
store: me.fooBarProducts.load(),
Usually a store should be only connected to a combobox like this:
store:'fooBarProducts'
Heres a fiddle to show you:
https://fiddle.sencha.com/#view/editor&fiddle/1kqj
Ext.application({
name : 'Fiddle',
launch : function() {
var yourStore=Ext.create('Ext.data.Store',{
fields:[{
name:'text'
},{
name:'value'
}],
data:[
{text:'test1',value:'testVal1'},
{text:'test2',value:'testVal2'},
{text:'test3',value:'testVal3'}
]
});
Ext.create({
xtype:'window',
width:300,
height:200,
items:[{
xtype:'combo',
displayField:'text',
valueField:'value',
store:yourStore
},{
xtype:'combo',
displayField:'text',
valueField:'value',
store:yourStore
}]
}).show();
}
});
have also a look here: http://docs.sencha.com/extjs/6.2.0/classic/Ext.form.field.ComboBox.html
for a combobox example
and here to store declaration:
http://docs.sencha.com/extjs/6.2.0/modern/Ext.data.Store.html
To load your store, you should call the store load out of combo declaration
If you use
store: somestore.load()
the combobox will bind to the value returned by load. The load function does not return anything, so the combo's store config is set to undefined.
What you want to do may be
me.fooBarProducts.load();
return me.templateComboBox = Ext.create('Ext.form.field.ComboBox', {
xtype:'combobox',
fieldLabel: 'FooBar Product',
store: me.fooBarProducts,
labelWidth: 155,
valueField: 'id',
displayField:'title',
editable: true,
allowBlank:true,
name:'fbproduct'
});
I have to append a value to the data fetched from the store before rendering it to the grid in ExtJs.
Please guide me on achieving the above mentioned functionality.
Currently the grid is populated in the following manner:
Ext.define("MyApp.view.ShowDetails",{
extend: 'Ext.grid.Panel',
requires:['MyApp.store.MyStore'],
store:'MyStore',
stateId: 'stateGrid',
selType : 'checkboxmodel',
selModel : {
mode : 'MULTI'
},
plugins : [Ext.create('Ext.grid.plugin.RowEditing', {
clicksToEdit : 2
})],
defaultType: 'textfield',
columns: [
{
header: 'Userid',
width: 150,
dataIndex: 'uid',
editor :
{
allowBlank : true
}
},
...
Yes this is possible,using the convert property in field declaration in MODEL
An Example:
{
name: 'uid',
type: 'string',
convert: function (value, record) {
if (!value)
return value+'D';
else
return value;
}
},
In my ViewModel, I create an inline data store for a ComboBox that I bind to in the View. The problem that I'm having is setting a default value for the ComboBox, based on one of the values in the store... I might be understanding binding here, so I'd like to hear any feedback.
OrderDetailsStatus model:
Ext.define('UserUI.model.OrderDetailsStatus', {
extend: 'Ext.data.Model',
alias: 'model.OrderDetailsStatus',
fields: [{
type: 'int',
name: 'statusId'
},
{
type: 'string',
name: 'status'
}]
});
ViewModel:
stores: {
/* TODO: This could eventually become an AJAX call, but for right now,
* it's an inline data store... the statusId's are currently unused */
orderDetailsStatusStore: {
model: 'UserUI.model.OrderDetailsStatus',
proxy: {
type: 'memory'
},
data: [
{ status: 'All', statusId: 1 },
{ status: 'Correct', statusId: 2 },
{ status: 'Incorrect', statusId: 3 }
]
}
}
In the View:
{
xtype: 'combo',
fieldLabel: 'Status',
bind: {
store: '{orderDetailsStatusStore}'
},
valueField: 'status',
displayField: 'status',
queryMode: 'local',
value: 'All',
listeners: {
select: 'onSelectComboBoxStatus'
}
}
Using the value: 'All' gives me an error about the model not existing:
TypeError: Model is not a constructor: ext-all...ebug.js (line 122343, col 33)
record = new Model(dataObj);
I'm assuming this is because the bound store hasn't been fully loaded in yet? If I debug the code, at that line, Model is undefined, and the store doesn't have any data. Any help would be appreciated.
Fiddle updated with fix in 5.1.0
<iframe src="https://fiddle.sencha.com/fiddle/m3g"></iframe>
I've created a simple fiddle that replicates yours -
https://fiddle.sencha.com/#fiddle/m3g
I have a dataview that gets loaded with a parameter. I render this data in a template. I am trying to use the same data in which there is a nested item to render to a grid.
The data looks like this:
{
"myJson": {
"name": "abc",
"type": "faulty",
"notes": [
{
"date": "01-01-1970",
"note": "test note"
},
{
"date": "01-02-1970",
"note": "test note 2"
}
]
}
}
The store:
proxy: {
type: 'ajax',
url: '/api/detail/',
reader: {
type: 'json',
root: 'myJson'
}
}
Model:
{
name:'name',
},
{
name:'type',
},
{
name:'notes',
mapping:'notes'
},
Template:
{name} - {type}
That all works. What I'd like to do is use the notes chunk to display in a grid. Problem is I can't get it to read the notes group.
var notesListView = new Ext.list.ListView({
store: 'myStore',
multiSelect: false,
width:'100%',
id:'notesList',
columns: [{
header: 'Date',
width: 75,
dataIndex: 'date'
},{
header: 'Note',
width: 150,
dataIndex: 'note',
}]
});
Is it even possible to do this? Or do I need to create a new store and model to use this group of data in a grid?
I've tried mapping to notes.date, for instance, in both the model
name:'note_date',
mapping:'notes.date'
and in the grid itself
dataIndex:'notes.date'
neither of which worked.
I've also tried using renderer but this doesn't work either as it's an array
renderer:function(value, metaData, record, rowIndex, colIndex, store){
var value = value.date;//won't work; it needs an index a la value[0].date
return value;
}
You could create a nested model with the same data you are receiving. It would be like this
Ext.define("JsonModel", {
extend: 'Ext.data.Model',
fields: ['name','type'],
hasMany: [{
model: 'Note',
name: 'notes'
}]
});
Ext.define("Note", {
extend: 'Ext.data.Model',
fields: [
'date',
'note']
});
This way you could access the children of any give record like this
var jsonRecordChildren = jsronRecord.notes()
The variable jsonRecordChildren that I just created is of the type Store, so you could easily assign it to the attribute store of a grid.
Ext.create('Ext.grid.Panel', {
store: selectedRecord.notes(),
columns: [
{ text: 'Date', dataIndex: 'date' },
{ text: 'Note', dataIndex: 'note'}
],
renderTo: Ext.getBody()
});
http://jsfiddle.net/alexrom7/rF8mt/2/
I have two models: Page and Department. I am showing pages in a list view in extjs and I would like to display the Department's Name instead of the department_id in the List view. I have not gotten to the part of actually adding the department to the page VIA the GUI, only through direct db insert statements, but I would like to at least be able to display the department name in the list view.
I have the following so far, this is showing department_id
models
Ext.define('ExtMVC.model.Department', {
extend: 'Ext.data.Model',
fields: ['name']
});
Ext.define('ExtMVC.model.Page', {
extend: 'Ext.data.Model',
fields: ['title','body','department_id'],
associations: [
{type: 'belongsTo', model: 'Department'}
]
});
stores
Ext.define('ExtMVC.store.Pages', {
extend: 'Ext.data.Store',
model: 'ExtMVC.model.Page',
autoLoad: true,
proxy: {
type: 'rest',
url: '/admin/pages',
format: 'json'
}
});
Ext.define('ExtMVC.store.Departments', {
extend: 'Ext.data.Store',
model: 'ExtMVC.model.Department',
autoLoad: true,
proxy: {
type: 'rest',
url: '/admin/departments',
format: 'json'
}
});
List View
Ext.define('ExtMVC.view.page.List' ,{
extend: 'Ext.grid.Panel',
alias : 'widget.pagelist',
title : 'All Pages',
store: 'Pages',
initComponent: function() {
this.tbar = [{
text: 'Create Page', action: 'create'
}];
this.columns = [
{header: 'Title', dataIndex: 'title', flex: 1},
{header: 'Department', dataIndex: 'department_id', flex: 1}
];
this.callParent(arguments);
}
});
controller (fwiw)
Ext.define('ExtMVC.controller.Pages', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
'pagelist': {
itemdblclick: this.editPage
},
'pagelist > toolbar > button[action=create]': {
click: this.onCreatePage
},
'pageadd button[action=save]': {
click: this.doCreatePage
},
'pageedit button[action=save]': {
click: this.updatePage
}
});
},
onCreatePage: function () {
var view = Ext.widget('pageadd');
},
onPanelRendered: function() {
console.log('The panel was rendered');
},
doCreatePage: function (button) {
var win = button.up('window'),
form = win.down('form'),
values = form.getValues(),
store = this.getPagesStore();
if (form.getForm().isValid()) {
store.add(values);
win.close();
this.getPagesStore().sync();
}
},
updatePage: function (button) {
var win = button.up('window'),
form = win.down('form'),
record = form.getRecord(),
values = form.getValues(),
store = this.getPagesStore();
if (form.getForm().isValid()) {
record.set(values);
win.close();
this.getPagesStore().sync();
}
},
editPage: function(grid, record) {
var view = Ext.widget('pageedit');
view.down('form').loadRecord(record);
},
stores: [
'Pages',
'Departments'
],
models: [
'Page'
],
views: [
'page.List',
'page.Add',
'page.Edit'
]
});
Ext's associations have visibly not been designed for use in stores, but rather for working with single records... So, I agree with what has already been said, that you'd better flatten your model on the server-side. Nevertheless, it is possible to achieve what you want.
The association won't load your associated model (i.e. Department) until you call the generated getter method (i.e. getDepartment()). Trying to go this way, that is calling this method for each Page record loaded in your store would require an incredible amount of hack because the grid reacts synchronously to the refresh event of the store, while the getDepartment() method returns asynchronously...
That's why you will have to load your departments data in the same request that loads your pages. That is, your server must return records of the form:
{title: 'First Page', body: 'Lorem', department_id: 1, department: {name: 'Foo'}}
In order for your Page model's proxy to consume this, you need to configure your association this way:
Ext.define('ExtMVC.model.Page', {
// ...
associations: [{
type: 'belongsTo'
// You need the fully qualified name of your associated model here
// ... which will prevent Ext from generating everything magically
,model: 'ExtMVC.model.Department'
// So you must also configure the getter/setter names (if you need them)
,getterName: 'getDepartment'
// Child data will be loaded from this node (in the parent's data)
,associationKey: 'department'
// Friendly name of the node in the associated data (would default to the FQ model name)
,name: 'department'
}]
});
Then comes the really ugly part. Your grid's columns cannot access the associated data with the classic dataIndex property. However, provided the associated records have already been loaded, this can be accessed from a TemplateColumn like this:
{
header: 'Department'
,xtype: 'templatecolumn'
,tpl: '{department.name}'
}
Unfortunately, that will prevent you from using some more appropriate column class (date column, etc.), that you may have configured globally. Also, this column loses track of the model field it represents, which means that some features based on introspection won't be able to do their magic (the grid filter ux, for example, uses the field type to decide automatically on the type of filter).
But in the particular case you've exposed, that won't matter...
Complete Example
Here's what it gives when you bring it all together (or see it in action)...
Ext.define('ExtMVC.model.Department', {
extend: 'Ext.data.Model',
fields: ['name'],
proxy: {
type: 'memory'
,reader: 'json'
,data: [
{id: 1, name: 'Foo'}
,{id: 2, name: 'Bar'}
,{id: 30, name: 'Baz'}
]
}
});
Ext.define('ExtMVC.model.Page', {
extend: 'Ext.data.Model',
fields: ['title','body','department_id'],
associations: [{
type: 'belongsTo'
,model: 'ExtMVC.model.Department'
,getterName: 'getDepartment'
,associationKey: 'department'
,name: 'department'
}],
proxy: {
type: 'memory'
,reader: 'json'
,data: [
{title: 'First Page', body: 'Lorem', department_id: 1, department: {name: 'Foo'}}
,{title: 'Second Page', department: {name: 'Bar'}}
,{title: 'Last Page', department: {name: 'Baz'}}
]
}
});
Ext.define('ExtMVC.store.Pages', {
extend: 'Ext.data.Store',
model: 'ExtMVC.model.Page',
autoLoad: true
});
Ext.define('ExtMVC.view.page.List', {
extend: 'Ext.grid.Panel',
alias : 'widget.pagelist',
title : 'All Pages',
store: Ext.create('ExtMVC.store.Pages'),
initComponent: function() {
this.tbar = [{
text: 'Create Page', action: 'create'
}];
this.columns = [
{header: 'Title', dataIndex: 'title', flex: 1}
,{header: 'Department', xtype: 'templatecolumn', flex: 1, tpl: '{department.name}'}
];
this.callParent(arguments);
}
});
Ext.widget('pagelist', {renderTo: 'ct', height: 200});
As one of the comments mentions, it would be a lot simpler to bind the department name to the Pages model when working with a grid/list. Denormalisation for display is not a bad thing.
An alternative is perhaps reverse the modelling, in that you could say the a department 'hasMany' Pages.
This allows you define the primary and foreign keys on the relationship and then your department store will (per row) have a automatic 'pages()' store which will contain the child info.
I've typically done this for master/detail forms where I want to bind 'pages()' to a list/grid, but keep the department model as the master record on a form for example.
Why shouldn't use renderer config function on the grid's column ?
This implies to (auto)load the departments store upfront on application launch (you might need it in more places anyway, think of grid cell editor as full department list).
{
name:'Department',
dataIndex:'department_id',
renderer: function(deptId){
return Ext.data.StoreManager.lookup('Departments').getById(deptId).get('name');
}
}
PS: I was using myself denormalisation for display, but doesn't feel good ;)