ExtJS 4 - Reuse components in MVC architecture - javascript

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.

Related

Get form filed by using reference

I have a form as follows
this.stdForm = new Ext.form.Panel(this.createStdForm());
this.stdForm.getForm().load({
params: { action: 'load', id: 1234, dep: 'std' },
success: this.formLoaded,
scope: this
});
createStdForm: function () {
var flds = {
items: [
{ fieldLabel: 'Roll Number', name: 'ROLL_NUMBER', xtype: 'displayfield' } }
]
};
var fldSet = {
title: 'Student Information',
xtype: 'panel',
defaultType: 'fieldset',
defaults: { autoHeight: true, baseCls: 'x-fieldset-noborder', reference:'RollNumber'},
items: [flds]
};
return {
layout: 'column',
border: false,
defaults: {
border: false
},
url: this.getUrl(),
items: [
col1
]
};
}
and on formloaded function, I am trying to get reference of roll number field
formLoaded:function (form, action) {
form.getreferences('RollNumber') // not working
form.lookupreference(); // not working
}
Can anybody tell me what I am doing wrong.
In your example - just add referenceHolder: true to the panel:
If true, this container will be marked as being a point in the
hierarchy where references to items with a specified reference config
will be held. The container will automatically become a
referenceHolder if a controller is specified. See the introductory
docs for Ext.container.Container for more information about references
& reference holders.
Working example: https://fiddle.sencha.com/#fiddle/1d7t

Default value in grid

I have a grid/list:
items: [
{
xtype: 'gridpanel',
reference: 'list',
resizable: false,
width: 200,
title: '',
forceFit: true,
bind: {
store: '{schedules}'
},
columns: [
{
xtype: 'gridcolumn',
dataIndex: 'revision',
text: 'Revision'
}
],
I want to add a listener so that the record at index 0 in the store is selected by default.
I've tried playing with selModel but its not working as intended.
Do it on viewready event:
{
xtype: 'gridpanel',
listeners: {
'viewready': function(g) {
g.getSelectionModel().select(0);
}
},
// ....
}
Example: https://fiddle.sencha.com/#fiddle/qe6
Listen to the store load event (as example in the controller):
onLaunch: function () {
var me = this,
grid = me.getGrid(),
store = me.getGroupsStore();
store.load({
callback: function(records, operation, success) {
grid.getSelectionModel().select(0);
},
scope: this
});
},

ExtJS: store loaded, records in form but not in fields

I'm struggling with my application right in the beginning.
this.getScoresStore().on('load', function(score, records) {
var view = Ext.getCmp('scoreView');
view.down('form').loadRecord(records[0].data);
console.log(view.down('form').getRecord());
console.log(view.down('form').getValues());
});
After the store is loaded, I add the records to the form. Console says it's added, however the fields keep beeing empty.
Object { playerOne="301", playerTwo="301" }
Object { playerOne="", playerTwo="" }
Anyone got Ideas what could be wrong?
Controller:
Ext.define('Darts.controller.Scores', {
extend: 'Ext.app.Controller',
views: [
'score.View',
'score.Hit'
],
stores: [
'Scores'
],
models: [
'Score'
],
init: function() {
this.getScoresStore().on('load', function(score, records) {
var view = Ext.getCmp('scoreView');
view.down('form').loadRecord(records[0].data);
console.log(view.down('form').getRecord());
console.log(view.down('form').getValues());
});
this.control({
'scoreView' : {
afterrender: this.formRendered
}
});
},
formRendered: function(obj) {
console.log(obj.down('form').getRecord());
console.log('form rendered');
}
});
Views:
Ext.define('Darts.view.score.Hit' ,{
extend: 'Ext.panel.Panel',
alias : 'widget.scoreHit',
title : 'Hits',
score : 'Scores',
initComponent: function() {
this.items = [
{
xtype: 'form',
items: [
{
xtype: 'textfield',
name : 'playerTwo',
fieldLabel: 'Player 1'
}
]
}
];
this.callParent(arguments);
}
});
Ext.define('Darts.view.score.View' ,{
extend: 'Ext.panel.Panel',
alias : 'widget.scoreView',
id : 'scoreView',
title : 'Player Scores',
score : 'Scores',
initComponent: function() {
this.items = [
{
xtype: 'form',
items: [
{
xtype: 'numberfield',
name : 'playerOne',
fieldLabel: 'Player 1'
}, {
xtype: 'textfield',
name : 'playerTwo',
fieldLabel: 'Player 2'
}
]
}
];
this.buttons = [
{
text: 'Start Game',
action: 'start'
}
];
this.callParent(arguments);
}
});
Store
Ext.define('Darts.store.Scores', {
extend: 'Ext.data.Store',
model : 'Darts.model.Score',
autoLoad: true,
proxy: {
type: 'ajax',
api: {
read: 'data/scores.json',
update: 'data/updateScores.json'
},
reader: {
type: 'json',
root: 'scores',
successProperty: 'success'
}
}
});
Model:
Ext.define('Darts.model.Score', {
extend: 'Ext.data.Model',
fields: ['playerOne', 'playerTwo']
});
Data:
{
success: true,
scores: [
{id: 1, playerOne: '301', playerTwo: '301'}
]
}
I've tried numberfields, textfields as well as changing the data fom with ' to without ' and mixed.... nothing seems to help me.
The fields are rendered before store is loaded (test output still in the code)
I'm really out of ideas here and I've seen many topics, but none fits to my problem or fixes my problem. The form fields always keeps beeing empty.
I think your issue is that you need to pass a Model record into loadRecord method not the underlying data. So try changing line 3 to
view.down('form').loadRecord(records[0]);
As a side note, it's a bit odd to load the entire store just to get at a single record.
You might want to explore Model.load( id, {callback config} ) way of loading exact record that you need.

Trouble with Sencha Touch MVC

I'm trying to learn how to use Sencha Touch to build web apps. I've been following the tutorial Here and I am a bit stuck. Below have created one controller, two views and a model (All other code is copy & paste from the tutorial). The first view, Index works great. However if I try to access the second, it brings up a blank page, none of the toolbar buttons work and it doesn't fire the alert.
If I do comment out the line this.application.viewport.setActiveItem(this.editGyms);, the alert will fire, but obviously, it doesn't render the page.
I've looked at a couple other tutorials, and they seem to also be using the setActiveItem member to switch views.. Am I missing something, or do I have to somehow deactivate the first view to activate the second or something?
HomeController.js
Ext.regController('Home', {
//Index
index: function()
{
if ( ! this.indexView)
{
this.indexView = this.render({
xtype: 'HomeIndex',
});
}
this.application.viewport.setActiveItem(this.indexView);
},
editGyms: function()
{
if ( ! this.editGyms)
{
this.editGyms = this.render({
xtype: 'EditGymStore',
});
}
this.application.viewport.setActiveItem(this.editGyms);
Ext.Msg.alert('Test', "Edit's index action was called!");
},
});
views/home/HomeIndexView.js
App.views.wodList = new Ext.List({
id: 'WODList',
store: 'WODStore',
disableSelection: true,
fullscreen: true,
itemTpl: '<div class="list-item-title"><b>{title}</b></div>' + '<div class="list-item-narrative">{wod}</div>'
});
App.views.HomeIndex = Ext.extend(Ext.Panel, {
items: [App.views.wodList]
});
Ext.reg('HomeIndex', App.views.HomeIndex);
views/home/EditGymStore.js
App.views.EditGymStore = Ext.extend(Ext.Panel, {
html: 'Edit Gyms Displayed Here',
});
Ext.reg('EditGymStore', App.views.EditGymStore);
models/appModel.js
Ext.regModel('WOD', {
idProperty: 'id',
fields: [
{ name: 'id', type: 'int' },
{ name: 'date', type: 'date', dateFormat: 'c' },
{ name: 'title', type: 'string' },
{ name: 'wod', type: 'string' },
{ name: 'url', type: 'string' }
],
validations: [
{ type: 'presence', field: 'id' },
{ type: 'presence', field: 'title' }
]
});
Ext.regStore('WODStore', {
model: 'WOD',
sorters: [{
property: 'id',
direction: 'DESC'
}],
proxy: {
type: 'localstorage',
id: 'wod-app-localstore'
},
// REMOVE AFTER TESTING!!
data: [
{ id: 1, date: new Date(), title: '110806 - Title1', wod: '<br/><br/>Desc1</br><br/>' },
{ id: 1, date: new Date(), title: '110806 - Title1', wod: '<br/><br/>Desc2</br><br/>' }
]
});
viewport.js with toolbar
App.views.viewport = Ext.extend(Ext.Panel, {
fullscreen: true,
layout: 'card',
cardSwitchAnimation: 'slide',
scroll: 'vertical',
styleHtmlContent: true,
style: 'background: #d8e2ef',
dockedItems: [
{
xtype: 'toolbar',
title: 'The Daily WOD',
buttonAlign: 'right',
items: [
{
id: 'loginButton',
text: 'Login',
ui: 'action',
handler: function() {
Ext.Msg.alert('Login', "This will allow you to Login!");
}
},
{
xtype: 'spacer'
},
{
xtype: 'button',
iconMask: true,
iconCls: 'refresh',
ui: 'action',
handler: function() {
Ext.Msg.alert('Refresh', "Refresh!");
}
}]
},
],
});
Thanks for the help!!
In HomeController.js your action function (editGyms) is the same name as the variable you're using for your view (this.editGyms) so when it tries to setActiveItem(this.editGyms) its actually passing the controllers action function rather than the results of this.render({...})
Either change the name of the controller action or change the name of the variable you use to hold the view.. like
editGyms: function() {
if ( ! this.editGymView) {
this.editGymView = this.render({
xtype: 'EditGymStore',
});
}
this.application.viewport.setActiveItem(this.editGymView);
Ext.Msg.alert('Test', "Edit's index action was called!");
}
I am colleagues with the guy that wrote the tutorial you are referring to. You should add a comment on that post because I described your problem to him and he said that he knows the solution.
You can just add a link to this page so that you won't need to describe everything again.
Also he will (very) soon publish the 3rd part of that tutorial that will cover some similar things.

Displaying views that contain 'partial'/'nested' widgets in EXTjs 4

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
});

Categories

Resources