I've read a tutorial about creating DataView with custom comonent for each element from Store. It says about mapping fields of record to components in DataItem, but my question is how to define order in which components will be added to DataItem.
For example, I have such DataItem:
Ext.define('demo.view.CottageItem', {
extend: 'Ext.dataview.component.DataItem',
requires: ['Ext.Label', 'Ext.Array', 'Ext.Img'],
xtype: 'listitem',
config: {
layout: {
type: 'hbox',
align: 'center',
pack: 'start',
},
title: {
flex: 1
},
photo: {
width: '140px',
height: '140px'
},
dataMap: {
getTitle: {
setHtml: 'title'
},
getPhoto: {
setSrc: 'photo'
}
},
styleHtmlContent: true
},
applyTitle: function(config) {
return Ext.factory(config, Ext.Label, this.getTitle());
},
updateTitle: function(newTitle, oldTitle) {
if (oldTitle) {
this.remove(oldTitle);
}
if (newTitle) {
this.add(newTitle);
}
},
applyPhoto: function(config) {
return Ext.factory(config, Ext.Img, this.getPhoto());
},
updatePhoto: function(newImage, oldImage) {
if (oldImage) {
this.remove(oldImage);
}
if (newImage) {
this.add(newImage);
}
},
});
It has layout type hbox, so I want photo to be added first and then title. So that title would be to the right of the photo. How can I achieve this?
Another question is, is there a way to add another container inside this DataItem, in which can be inserted my Label and Image?
UPDATE:
Now my layout looks like this:
|Label(title)| |Image(Photo)|
I want it look like this:
|Image(photo)| |Label(title)|
Finally I understood how to solve my problem, here is my code:
Ext.define('demo.view.CottageItem', {
extend: 'Ext.dataview.component.DataItem',
requires: ['Ext.Label', 'Ext.Array', 'Ext.Img'],
xtype: 'listitem',
config: {
layout: {
type: 'hbox',
align: 'center',
pack: 'start',
},
title: {
style: {
'font-weight': 'bold'
}
},
photo: {
width: '140px',
height: '140px'
},
desc: {},
dataMap: {
getTitle: {
setHtml: 'title'
},
getPhoto: {
setSrc: 'photo'
},
getDesc: {
setHtml: 'desc'
},
},
styleHtmlContent: true,
items: [
{
xtype: 'panel',
itemId: 'photoContainer',
},
{
xtype: 'panel',
layout: {
type: 'vbox'
},
itemId: 'textContainer',
flex: 1,
}
],
},
applyTitle: function(config) {
return Ext.factory(config, Ext.Label, this.getTitle());
},
updateTitle: function(newTitle, oldTitle) {
if (oldTitle) {
this.getComponent('textContainer').remove(oldTitle);
}
if (newTitle) {
this.getComponent('textContainer').add(newTitle);
}
},
applyPhoto: function(config) {
return Ext.factory(config, Ext.Img, this.getPhoto());
},
updatePhoto: function(newImage, oldImage) {
if (oldImage) {
this.getComponent('photoContainer').remove(oldImage);
}
if (newImage) {
this.getComponent('photoContainer').add(newImage);
}
},
applyDesc: function(config) {
return Ext.factory(config, Ext.Label, this.getDesc());
},
updateDesc: function(newDesc, oldDesc) {
if (oldDesc) {
this.getComponent('textContainer').remove(oldDesc);
}
if (newDesc) {
this.getComponent('textContainer').add(newDesc);
}
},
});
Instead of adding components to root, I defined two panels (placeholders) inside DataItem. I access them using getComponent method and add my component to desired place. The only problem here is that order of items added to textContainer is undefined, but in my case I got desired order.
Change sort order of the backend store? I'm not sure about component based DataView, but element-based matches to store sorting.
To properly set (or change) sorting you have to read Store's config sorters property or sort function
Cheers, Oleg
Related
This question is part of How to set defaults for Grid columns within initComponent and posted here independently through #scebotari66 advice on main post.
As you'll notice below; there is Ext.Array.map to define defaults for related function.
// Statment
initComponent: function () {
var me = this;
me.items = Ext.Array.merge(
me.getFormSt(),
Ext.Array.map(me.getForm(), function (listFldConfig) { //Aim to using the array map function to set flex property for subset fields
listFldConfig.flex = 1;
return listFldConfig;
}),
me.getFormEnd()
);
me.callParent(arguments)
},
// Implementation
getForm: function () {
var me = this;
var form = [
{ // Array.map func. sets `flex` to this obj.
xtype: 'fieldcontainer',
layout: { type: 'vbox', align: 'stretch', pack: 'start' },
items: [
{
xtype: 'fieldcontainer',
layout: 'hbox',
items: [
{
xtype: 'foofield',
//flex: 1 //But I need to set `flex` as default for this obj. in nested items array
},
{
xtype: 'barfield',
//flex: 1 //But I need to set `flex` as default for this obj. in nested items array
}
The thing is this implementation is works as expected but on this situation I'm creating a fieldcontainer object which is include all other things and items inside. And Array.map sets flex config only to first fieldcontainer obj. I need to define flex config only for nested items which has foofield and barfield.
Defaults are defined using the defaults config on containers:
xtype: 'fieldcontainer',
layout: 'hbox',
defaults: {
flex: 1
},
items: [
{
xtype: 'foofield',
},
{
xtype: 'barfield',
}
To cover nested containers, you can nest multiple defaults configs inside each other:
defaults: {
defaults: {
flex: 1
},
flex: 1
}
Please note that an xtype config as part of the defaults object can lead to undesired results, and that you should use the defaultType config to define the default type of child elements of a container.
Through #NarendraJadhav 's opinion; created my own structure...
Definition;
Ext.define('MyApp.BaseFldCon', {
extend: 'Ext.form.FieldContainer',
xtype: 'basefldcon'
});
Ext.define('MyApp.VBoxFldCon', {
extend: 'MyApp.BaseFldCon',
xtype: 'vboxfldcon',
layout: {
type: 'vbox',
align: 'stretch',
pack: 'start'
}
});
Ext.define('MyApp.HBoxFldCon', {
extend: 'MyApp.BaseFldCon',
xtype: 'hboxfldcon',
layout: {
type: 'hbox'
},
defaults: {
flex: 1
}
});
Implementation;
{
xtype: 'vboxfldcon',
items: [
{
xtype: 'hboxfldcon',
items: [
{
xtype: 'foofield',
},
{
xtype: 'barfield'
},
{
xtype: 'foocombo'
}
]
},
I have a template column in a grid which I am rendering a sparkline graph to: http://omnipotent.net/jquery.sparkline and when I sort the grid the canvas element that is rendered by the plugin is destroyed. Is there a way to stop the contents of a row being altered during a sort? I'm using ExtJS 4.2.3
Libraries:
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
<script src="//omnipotent.net/jquery.sparkline/2.1.2/jquery.sparkline.min.js"></script>
Code:
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.create("Ext.grid.Panel", {
store: new Ext.data.Store({
data: [
{
thing: '1'
},
{
thing: '1'
},
{
thing: '1'
},
{
thing: '1'
},
{
thing: '1'
}
],
fields: [
"thing"
]
}),
columns: [
{
text: 'Hello',
xtype: 'templatecolumn',
tpl: Ext.create('Ext.XTemplate', "<div id='1'></div>"),
flex: 1
},
{
text: 'Other',
flex: 1,
dataIndex: 'thing'
}
],
renderTo: Ext.getBody(),
listeners: {
boxready: function() {
$('#1').sparkline([1,2,3,4,5,6,7,8,9,10])
}
}
});
}
});
sortchange
listeners: {
boxready: function() {
$('#1').sparkline([1,2,3,4,5,6,7,8,9,10])
},
sortChange: function() {
// your logic here
}
}
I am creating a Sencha touch xtype, it will contain an invisible input file and a button; I would like to trigger the popup to choose a file when the button is pressed. This is what I've done so far:
config: {
baseCls: 'imageFileField',
layout: 'hbox',
items: [
{
xtype: 'label',
baseCls: Ext.baseCSSPrefix + 'form-label'
},
{
xtype: 'container',
layout: 'hbox',
flex: 1,
items: [
{
xtype: 'filefield',
hidden: true,
listeners: {
afterrender: function (cmp) {
cmp.fileInputEl.set({
accept: 'image/*'
});
}
}
},
{
xtype: 'label',
baseCls: Ext.baseCSSPrefix + 'form-label'
},
{
xtype: 'button',
margin: '5px',
docked: 'right',
ui: 'base_button',
iconCls: '',
width: '50px',
listeners: {
tap: function (view, e, eOpts) {
}
}
}
]
}
]
},
I know I should put something within the tap method, to navigate to the item and then trigger the event. I tried using this.up()/down(...) but I've never been able to get the invisible input. What is the right "path" to get there?
You can use Ext.ComponentQuery to find you element and set its properties. To find your element you have to first assign itemId to it.
xtype: 'filefield',
hidden: true,
itemId: 'chooseFile',
listeners: {
afterrender: function (cmp) {
cmp.fileInputEl.set({
accept: 'image/*'
});
}
}
Then you can put the following code in your button's listener event.
listeners: {
tap: function (view, e, eOpts) {
Ext.ComponentQuery.query("container > #chooseFile").show();
}
}
I have put all the controls in the controller and its working correctly:-
refs: {
filefield: 'filefield[name="fileField"]',
fileBtn: 'button[name="fileBtn"]'
},
control: {
"fileBtn": {
tap: function() {
this.getFilefield().show();
}
}
}
I'm having trouble understanding how I need to define and use the MVC model for my test EXTjs4 app. Consider the following structure.
app.js
Ext.application({
name: 'AM',
appFolder: 'app',
controllers: ['Cards', 'Fourscrum'],
launch: function () {
Ext.create('Ext.container.Viewport', {
defaults: { flex: 1 },
layout: {
type: 'hbox',
align: 'stretch',
},
items:
[
Ext.widget('Fourscrum')
]
});
Controller:
Cards.js
Ext.define('AM.controller.Cards', {
extend: 'Ext.app.Controller',
stores: ['BacklogCards', 'InprogressCards', 'ReviewCards', 'DoneCards', 'Cards', 'Priorities', 'Sizes'],
models: ['Card', 'Priority', 'Size'],
views: ['card.List', 'priority.prioritycombo', 'card.Edit'],
Fourscrum.js
Ext.define('AM.controller.Fourscrum', {
extend: 'Ext.app.Controller',
stores: ['BacklogCards', 'InprogressCards', 'ReviewCards', 'DoneCards', 'Cards', 'Priorities', 'Sizes'],
models: ['Card', 'Priority', 'Size'],
views: ['scrum.Fourscrum', 'card.List'],
view.scrum.Fourscrum.js
Ext.define('AM.view.scrum.Fourscrum', { // *** Variable
extend: 'Ext.panel.Panel',
alias: 'widget.Fourscrum', // *** Variable
width: 400,
height: 300,
layout: 'column',
title: 'Scrum', // *** Variable
items:
[
Ext.widget('cardlist',
{
alias: 'widget.backlogcardlist',
title: "Backlog",
store: 'BacklogCards'
}),
Ext.widget('cardlist',
{
alias: 'widget.backlogcardlist',
title: "Backlog",
store: 'BacklogCards'
}),
Ext.widget('cardlist',
{
alias: 'widget.inprogresscardlist',
title: "In Progress",
store: "InprogressCards"
}),
Ext.widget('cardlist',
{
alias: 'widget.reviewcardlist',
title: "Review",
store: "ReviewCards"
}),
Ext.widget('cardlist',
{
alias: 'widget.donecardlist',
title: "Done",
store: "DoneCards"
})
]
});
My ideal structure for this app is as follows:
Viewport defined (inside app.js)
which contains a Fourscrum.js view (which is just a panel)
which contains 4 different List.js views (which are just grids).
Trying to accomplish this, I currently get a few errors when i start messing with the above code:
Item undefined
namespace undefined
Does anyone know why this doesn't work?
PS. I can get this example to work if I replace my 'cardlist' widgets with panels directly defined in the Fourscrum view.
PPS. This also works properly if I forego the Fourscrum container panel all together :(
EDIT:
I felt my explanation was a little unclear so I've uploaded an image to help describe the program. I'm not sure where I need to define the stores, models, and views with this nested structure. So I've repeated it in both controllers. I hope that's not what is causing the problem.
EDIT2:
Ext.define('AM.view.card.List', {
extend: 'Ext.grid.Panel',
alias: 'widget.cardlist',
//title: 'List',
//store: 'Cards',
//multiSelect: true,
viewConfig: {
plugins: {
ptype: 'gridviewdragdrop',
dragGroup: 'ddzone',
dropGroup: 'ddzone'
}
},
// selType: 'cellmodel',
// plugins: [
// Ext.create('Ext.grid.plugin.CellEditing', {
// clicksToEdit: 1
// })
// ],
columns: [
{
header: 'ID',
dataIndex: 'external_id',
field: 'textfield',
width: 30
},
{
header: 'Name',
dataIndex: 'name',
field: 'textfield',
width: 150
},
{
header: 'Priority',
dataIndex: 'priority_id',
renderer: function (value) {
var display = '';
Ext.data.StoreManager.get("Priorities").each(function (rec) {
if (rec.get('id') === value) {
display = rec.get('short_name');
return false;
}
});
return display;
},
width: 60,
field: { xtype: 'PriorityCombo' }
},
{
header: 'Size',
dataIndex: 'size_id',
renderer: function (value) {
var display = '';
Ext.data.StoreManager.get("Sizes").each(function (rec) {
if (rec.get('id') === value) {
display = rec.get('short_name');
return false;
}
});
return display;
},
width: 60
},
{
xtype: 'actioncolumn',
width: 16,
items: [{
icon: 'Styles/Images/zoom.png', // Use a URL in the icon config
tooltip: 'Zoom In',
handler: function (grid, rowIndex, colIndex) {
var rec = grid.getStore().getAt(rowIndex);
alert("Edit " + rec.get('name'));
}
}]
}
]
});
I think I see a big problem in your code (if you pasted all of it).
In your view definitions if you are extending Ext components you MUST have the following function that ends in the callParent method like below.
initComponent: function() {
this.items = this.buildMyItems();
this.callParent(arguments);
},
buildMyItems: function(){
//my code
}
Robodude,
According to the Class guide on Sencha.com all widgets must be contained in properly named class files. I don't think you can simultaneously define and create your widgets in the panel definition.
Split out your definitions from the panel config. Also dont forget to enable the auto loader:
Ext.Loader.setConfig({
enabled : true
});
I have a list of users and if I click on an item in this list, a window opens. This is the same window for each user, and it's possible to have several window open at the same time. The window shows user informations so for this components I have the same store and the same model.
But if I load data in a specific window, I load the same data in all other open windows.
Ext.define('Cc.view.absence.Grid', {
extend: 'Ext.grid.Panel',
alias: 'widget.absencegrid',
border:false,
initComponent: function() {
Ext.apply(this, {
store: Ext.create('Ext.data.Store', {
model: 'Cc.model.Absence',
autoLoad: false,
proxy: {
type: 'ajax',
reader: {
type: 'json'
}
}
}),
columns: [
{header: 'Du', dataIndex: 'startdate', flex: 3, renderer: this.formatDate},
{header: 'Au', dataIndex: 'enddate', flex: 3, renderer: this.formatDate},
{header: 'Exercice', dataIndex: 'year', align: 'center', flex: 1},
{header: 'Statut', xtype:'templatecolumn', tpl:'<img src="../images/status-{statusid}.png" alt="{status}" title="{status}" />', align: 'center', flex: 1},
{header: 'Type d\'absence', dataIndex: 'absencetype', align: 'center', flex: 2},
{header: 'Commentaires', dataIndex: 'comment', flex: 6}
],
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
items: [
{ xtype: 'tbfill'},
{ xtype: 'button', text: 'Rafraichir', action: 'refresh', iconCls: 'item-rafraichir' }
]
}]
});
this.callParent(arguments);
},
formatDate: function(date) {
if (!date) {
return '';
}
return Ext.Date.format(date, 'l d F Y - H:i');
}
});
my controller :
Ext.define('Cc.controller.Absences', {
extend: 'Ext.app.Controller',
models: ['Absence', 'AbsenceHistory'],
views: [],
refs: [
{ ref: 'navigation', selector: 'navigation' },
{ ref: 'tabPanel', selector: 'tabpanel' },
{ ref: 'absencePanel', selector: 'absencepanel' },
{ ref: 'refreshButton', selector: 'absencepanel button[action=refresh]'},
{ ref: 'absenceGrid', selector: 'absencegrid' },
{ ref: 'absenceHistory', selector: 'absencehistory' },
],
init: function() {
this.control({
'absencepanel button[action=refresh]': {
click: this.onClickRefreshButton
},
'absencegrid': {
selectionchange: this.viewHistory
},
'absencegrid > tableview': {
refresh: this.selectAbsence
},
});
},
selectAbsence: function(view) {
var first = this.getAbsenceGrid().getStore().getAt(0);
if (first) {
view.getSelectionModel().select(first);
}
},
viewHistory: function(grid, absences) {
var absence = absences[0],
store = this.getAbsenceHistory().getGrid().getStore();
if(absence.get('id')){
store.getProxy().url = '/absence/' + absence.get('id') +'/history';
store.load();
}
},
onClickRefreshButton: function(view, record, item, index, e){
var store = this.getAbsenceGrid().getStore();
store.load();
},
});
other controller which create only one instance of absence.Panel :
Ext.define('Cc.controller.Tools', {
extend: 'Ext.app.Controller',
stores: ['Tools', 'User' ],
models: ['Tool'],
views: [],
refs: [
{ ref: 'navigation', selector: 'navigation' },
{ ref: 'tabPanel', selector: 'tabpanel' },
{ ref: 'toolList', selector: 'toollist' },
{ ref: 'toolData', selector: 'toollist dataview' }
],
init: function() {
this.control({
'toollist dataview': {
itemclick: this.loadTab
},
});
},
onLaunch: function() {
var dataview = this.getToolData(),
store = this.getToolsStore();
dataview.bindStore(store);
},
loadTab: function(view, record, item, index, e){
var tabPanel = this.getTabPanel();
switch (record.get('tab')) {
case 'absences':
if(Ext.getCmp('absence-panel')){
tabPanel.setActiveTab(Ext.getCmp('absence-panel'));
}
else {
var panel = Ext.create('Cc.view.absence.Panel',{
id: 'absence-panel'
}),
store = panel.getGrid().getStore();
panel.enable();
tabPanel.add(panel);
tabPanel.setActiveTab(panel);
store.getProxy().url = '/person/' + this.getUserId() +'/absences';
store.load();
}
break;
default:
break;
}
},
getUserId: function(){
var userStore = this.getUserStore();
var id = userStore.first().get('id')
return id;
}
});
the other controller which create many instances of absence.Panel :
Ext.define('Cc.controller.Agents', {
extend: 'Ext.app.Controller',
stores: ['Agents'],
models: ['Agent', 'Absence', 'AbsenceHistory'],
views: ['agent.List', 'agent.Window', 'absence.Panel', 'absence.Grid', 'absence.History'],
refs: [
{ ref: 'agentList', selector: 'agentlist' },
{ ref: 'agentData', selector: 'agentlist dataview' },
{ ref: 'agentWindow', selector: 'agentwindow' },
],
init: function() {
this.control({
'agentlist dataview':{
itemclick: this.loadWindow
},
});
},
onLaunch: function() {
var dataview = this.getAgentData(),
store = this.getAgentsStore();
dataview.bindStore(store);
},
loadWindow: function(view, record, item, index, e){
if(!Ext.getCmp('agent-window'+record.get('id'))){
var window = Ext.create('Cc.view.agent.Window', {
title: 'À propos de '+record.get('firstname')+' '+record.get('lastname'),
id: 'agent-window'+record.get('id')
}),
tabPanel = window.getTabPanel();
absencePanel = window.getAbsencePanel();
store = absencePanel.getGrid().getStore();
absencePanel.enable();
tabPanel.add(absencePanel);
tabPanel.setActiveTab(absencePanel);
store.getProxy().url = '/person/' + record.get('id') +'/absences';
store.load();
}
Ext.getCmp('agent-window'+record.get('id')).show();
}
});
and the absence.Panel view, container of absence.Grid :
Ext.define('Cc.view.absence.Panel', {
extend: 'Ext.panel.Panel',
alias: 'widget.absencepanel',
title: 'Mes absences',
iconCls: 'item-outils',
closable: true,
border: false,
disabled: true,
layout: 'border',
initComponent: function() {
this.grid = Ext.create('Cc.view.absence.Grid', {
region: 'center'
});
this.history = Ext.create('Cc.view.absence.History', {
region: 'south',
height: '25%'
});
Ext.apply(this, {
items: [
this.grid,
this.history
]
});
this.callParent(arguments);
},
getGrid: function(){
return this.grid;
},
getHistory: function(){
return this.history;
}
});
Yes. Here is more details explanation of what I am doing. I hope you have read the forum completely. There is still one information that we are not clear about. That is the exact use of using "stores" property in controller. IMHO Sencha team should explain the MVC in much more detail with complex examples.
Yes, what you have newly posted is correct. when you have create new view, create a new instance of the store! Now, from the forum discussions, people argue about MVC. I would definitly go with steffenk . What we are doing here is injecting a new instance of store to my view. And I am ignoring the stores property of the controller.
Here is an example:
This is my view. Its a panel (with user profile information) that I display on my tabpanel :
Ext.define('Dir.view.profile.View' ,{
extend: 'Ext.panel.Panel',
alias : 'widget.profileview',
title : 'Profile',
profileId: 1, // default and dummy value
initComponent: function() {
// configure necessary stuff, I access my store etc here..
// console.log(this.profileStore);
this.callParent(arguments);
},
// Other view methods goes here
});
Now, look at my controller:
Ext.define('Dir.controller.Profile', {
extend: 'Ext.app.Controller',
//stores: ['Profile'], --> Note that I am NOT using this!
refs: [
{ref:'cp',selector: 'centerpane'}
],
views: ['profile.View'],
init: function() {
// Do your init tasks if required
},
displayProfile: function(selectedId) {
// create a new store.. pass your config, proxy url etc..
var store = Ext.create('Dir.store.Profile',{profileId: selectedId});
console.log('Display Profile for ID ' + selectedId);
// Create instance of my view and pass the new store
var view = Ext.widget('profileview',{profileId: selectedId,profileStore: store});
// Add my new view to centeral panel and display it...
this.getCp().add(view);
this.getCp().setActiveTab(view);
}
});
My displayProfile() is called from some event listeners (Menu, Tree etc) and it passes a id. My controller using this id to setup a new store and view. I hope the above code gives you a clear picture of what I said yesterday.
In your controller, you will have to add Ext.require('Dir.store.Profile'); so that ExtJS know you have such a store. This is because we are not making use of stores property.
Now, you can want to reuse these created stores elsewhere, you can add them to the StoreManager. With this, you can access your created stores at any place, add and remove stores. But this comes with a overhead of you managing the instances of the store.
Why do you share the same instance of the store with different views? when you have a new view, create a new instance of the store. This will prevent updated data appearing on other windows when one is refreshed.