Related
This is my first time using a javascript framework, I would like to implement MVVM in my EXT JS application and the data is coming from my WEB API (ASP.NET FRAMEWORK).
My problem is that, I don't seem to understand how to fully use viewModel which looks up to my store. I successfully bound my ViewModel in my grid but now I don't know how to update the selected record using a form (modal) and sync my store (send update request through API)
I have a feeling that I'm doing it the wrong way. I don't know how to do this in fiddle so I'll just paste my code here.
Genre.js [Model]
Ext.define('VAM2.model.Genre', {
extend: 'VAM2.model.Base',
alias: 'model.genre',
fields: [
{name: 'GenreId', type: 'int'},
{name: 'Code', type: 'string'},
{name: 'CreatedBy', type: 'string'},
]
});
Genre.js [Store]
Ext.define('VAM2.store.Genre', {
extend: 'Ext.data.Store',
alias: 'store.genre',
model: 'VAM2.model.Genre',
autoLoad: false,
pageSize: 10,
storeId: 'GenreId',
proxy : {
type : 'rest',
actionMethods : {
read : 'GET'
},
cors:true,
url: 'https://localhost:44332/api/Genre/GetGenresExtJs',
api:{
create: 'https://localhost:44332/api/Genre/CreateGenreExtJS',
read: 'https://localhost:44332/api/Genre/GetGenresExtJs',
update: 'https://localhost:44332/api/Genre/EditGenreExtJS',
destroy: 'https://localhost:44332/api/Genre/CreateGenreExtJS'
},
useDefaultXhrHeader: false,
reader: {
type : 'json',
headers: { 'Accept': 'application/json' },
allDataOptions: {
associated: true,
persist: true
},
rootProperty : 'data',
totalProperty: 'total'
},
}
});
GenreViewModel.js - I'm not sure if this is okay but the read is working
Ext.define('VAM2.view.genre.GenreViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.genre',
requires:[
'VAM2.model.Genre'
],
stores: {
myGenres : {
model: 'VAM2.model.Genre',
autoLoad: true,
proxy : {
type : 'rest',
actionMethods : {
read : 'GET'
},
cors:true,
url: 'https://localhost:44332/api/Genre/GetGenresExtJs',
api:{
create: 'https://localhost:44332/api/Genre/CreateGenreExtJS',
read: 'https://localhost:44332/api/Genre/GetGenresExtJs',
update: 'https://localhost:44332/api/Genre/EditGenreExtJS',
destroy: 'https://localhost:44332/api/Genre/CreateGenreExtJS'
},
useDefaultXhrHeader: false,
reader: {
type : 'json',
headers: { 'Accept': 'application/json' },
allDataOptions: {
associated: true,
persist: true
},
rootProperty : 'data',
totalProperty: 'total'
},
}
}
},
data:{
title:'Sample Binding'
},
formulas: {
currentRecord: {
bind: {
bindTo: '{groupGrid.selection}', //--> reference configurated
//--> on the grid view (reference: groupGrid)
deep: true
},
get: function(record) {
return record;
},
set: function(record) {
if (!record.isModel) {
record = this.get('records').getById(record);
}
this.set('currentRecord', record);
}
}
}
});
View -- This is where it gets confusing. I don't know how to put the bounded data from grid to a form modal and then save and sync my store.
Ext.define('VAM2.view.genre.GenreList', {
extend: 'Ext.container.Container',
xtype: 'myGenreList',
requires: [
'VAM2.view.genre.GenreController',
'VAM2.view.genre.GenreViewModel',
'Ext.grid.column.Boolean',
'Ext.form.field.Checkbox',
'Ext.form.field.TextArea',
'Ext.form.field.Text'
],
controller: "genre",
viewModel: {
type: "genre"
},
width:'100%',
layout: {
type: 'vbox',
pack: 'start',
align: 'stretch'
},
style: {
backgroundColor: '#f5f5f5'
},
items: [{
xtype: 'grid',
reference: 'groupGrid', //--> used in the viewmodel,
bind: {
title: '{title}',
store: '{myGenres}'
},
columns: [{
text:'GenreIdField',
id:'GenreIdField',
dataIndex:'GenreId',
hidden:true
},{
text: 'Code',
dataIndex: 'Code',
flex:1
}, {
text: 'Created By',
dataIndex: 'CreatedBy',
flex: 1
}],
listeners:{
select:'onGenreSelected_FORMA' //--> I'm thinking this will trigger
//-> a form (modal) containing the data to update
}
}]
});
A fiddle example would be great! Thank you!
Screenshot:
This is where I would like to display form modal that can sync/update my store when I modify some data.
To do store.sync() you need to set values on the record first.
Example is without ViewModel:
https://fiddle.sencha.com/#fiddle/3isg&view/editor
select: function (grid, record) {
console.log(record);
let win = Ext.create("Ext.window.Window", {
title: 'Edit',
modal: true,
autoShow: true,
width: 400,
height: 500,
controller: {},
items: [{
xtype: 'form',
reference: 'form',
fieldLabel: 'name',
items: [{
xtype: 'textfield',
name: 'name'
}]
}],
buttons: [{
text: 'cancel',
handler: function () {
win.close();
}
}, {
text: 'save',
handler: function () {
var values = this.lookupController().lookup('form').getValues();
record.set(values);
grid.getStore().sync({
scope: this,
success: function () {
win.close();
Ext.toast({
html: 'Well done',
align: 't'
});
},
failure: function () {
Ext.toast({
html: 'Problem occurred',
align: 't'
});
}
});
}
}],
listeners: {
afterrender: function () {
this.lookupController().lookup('form').loadRecord(record);
}
}
})
}
I am using a rallymilestonecombobox to display milestones.
I am selecting the milestone and getting the correct value when I output to console.log.
When I select the new milestone I update the filter.
The filter isn't loading or updating in the store.
Initially I tried filters: myFilters but that didn't work in the artifact.Store.
It worked when I used
"filters:
[{
property : 'Milestones',
operator : 'contains',
value : myFilters.value
}],"
But I am not successful in updating the filter on the update ".load".
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
myStore: undefined,
storyGrid: undefined,
storyStore: undefined,
// Intital Layout
width: 1600,
height: 1200,
items: [{
xtype: 'container',
itemId: 'pulldown-container',
padding: '25,5,5,25',
layout: {
type: 'hbox',
}
},
{
xtype: 'container',
itemId: 'group-container',
layout: {
type: 'hbox',
},
items: [{
title: 'Stories',
xtype: 'container',
itemId: 'story-grid-container',
padding: '20,5,5,25',
layout: {
type: 'vbox',
}
},
{
title: 'Story info',
xtype: 'tabpanel',
itemId: 'story-info-container',
padding: '5,5,5,25', //top, right, bottom, left).
autoScroll: true,
layout: {
type: 'vbox',
},
}
]
}
],
launch: function() {
// Load Releases in ComboBox
// this._loadReleases();
this._loadMilestones();
},
_loadMilestones: function() {
// Create Milestone ComboBox
var milestoneComboBox = {
xtype: 'rallymilestonecombobox',
itemId: 'milestone-combo-box',
fieldLabel: 'Milestones',
labelAlign: 'right',
width: 300,
listeners: {
ready: this._loadData,
select: this._loadData,
scope: this
}
};
this.down('#pulldown-container').add(milestoneComboBox);
},
//construct filters for given milestone
_getMilestoneFilters: function(milestoneValue) {
var milestoneFilter = Ext.create('Rally.data.wsapi.Filter', {
property: 'Milestone',
operation: '=',
value: milestoneValue
});
return milestoneFilter;
},
//Get data from rally
_loadData: function() {
var selectedMilestoneRef = this.down('#milestone-combo-box').getRecord().get('_ref');
var myFilters = this._getMilestoneFilters(selectedMilestoneRef);
console.log(myFilters);
//if store exists, load new data
if (this.myStore) {
this.myStore.setFilter(myFilters);
this.myStore.load();
} else {
//create store
this.myStore = Ext.create('Rally.data.wsapi.artifact.Store', {
models: ['User Story', 'Defect'],
autoLoad: true,
filters: [{
property: 'Milestones',
operator: 'contains',
value: myFilters.value
}],
listeners: {
load: function() {
this._onStoriesForMilestoneLoad();
},
scope: this
},
fetch: ['FormattedID', 'Name']
});
}
},
_onStoriesForMilestoneLoad: function() {
if (!this.down('#my-grid')) {
var gridListeners = {
itemclick: {
fn: function(record, item) {
this._createStoryInfo(record, item);
this._createRevisionInfo(record, item);
}
},
scope: this
};
var columnCfgs = ['FormattedID', 'Name'];
this._createGrid('my-grid', this.myStore, gridListeners, '#story-grid-container', columnCfgs, 600, 775, null);
}
},
_createGrid: function(id, theStore, theListeners, container, theColumnCfgs, gridwidth, gridheight, tabTitle) {
var storyGrid = {
title: tabTitle,
xtype: 'rallygrid',
itemId: id,
store: theStore,
listeners: theListeners,
context: this.getContext(),
columnCfgs: theColumnCfgs,
enableEditing: false,
showRowActionsColumn: false,
enableScheduleStateClickable: false,
verticalScroller: false,
showPagingToolbar: false,
width: gridwidth,
height: gridheight,
forceFit: true,
};
this.down(container).add(storyGrid);
},
});
I'd check out this example:
https://help.rallydev.com/apps/2.1/doc/#!/example/filterable-grid
It's a little different than your use case, but the mechanics are still the same.
The important bit is this:
var myFilters = this._getMilestoneFilters(selectedMilestoneRef);
if (this.myStore) {
this.myStore.clearFilter(true);
this.myStore.filter([myFilters]); //notice, it's an array
}
There's also an app baseclass to make it easy to work with timebox scoping since it is such a common use case:
https://help.rallydev.com/apps/2.1/doc/#!/guide/timebox_filtering
You could extend Rally.app.TimeboxScopedApp, set scopeType to milestone and go from there. It will handle creating the milestone combo for you. And it will also automatically support working on milestone scoped custom pages in Rally.
I think you're probably really close to working code in your existing implementation, but worth a consideration to look into TimeboxScopedApp...
I had to update the Filter value specifically and it worked.
I should have known since I had to do this when creating the store.
//if store exists, load new data
if(this.myStore)
{
this.myStore.clearFilter(true);
this.myStore.filter([
{
property : 'Milestones',
operator : 'contains',
value : myFilters.value
}]);
this.myStore.load();
}
else
{
//create store
this.myStore = Ext.create('Rally.data.wsapi.artifact.Store',
{
first I thought it is a simple problem however I could not solve it anyway.
I have a extjs gridpanel, its store and model. From controller, I can insert new records to store, when I use firebug and debug, I can list all the new records in the store (panel.store.data.items) however in the gridview I cannot make it visible.
Could you please tell me where and what I am missing? Why the records are not listed in the grid?
This is my model
Ext.define('BOM.model.PaketModel', {
extend: 'Ext.data.Model',
fields: [
{ name: 'seriNo', type: 'string' },
{ name: 'tutar', type: 'string' },
]
});
This is store
Ext.define('BOM.store.PaketStore', {
extend: 'Ext.data.Store',
model: 'BOM.model.PaketModel',
proxy: {
type: 'memory',
reader: {
type: 'json',
root: 'data',
},
writer: {
type: 'json',
root: 'data',
},
},
});
This is the method I add new rows
addNew: function () {
this.getPaketWindow().returnRowEdit().cancelEdit();
this.getPaketWindow().getStore().insert(0, new BOM.model.PaketModel());
this.getPaketWindow().returnRowEdit().startEdit(0, 0);
}
UPDATE VIEW
Ext.define('BOM.view.PaketCreate', {
extend: 'Ext.grid.Panel',
alias: 'widget.paketcreate',
bodyPadding: 5,
layout: 'fit',
header:false,
initComponent: function () {
this.columns = [
{ text: 'Seri No', flex: 2, sortable: true, dataIndex: 'seriNo', field: {xtype: 'textfield'} },
{ text: 'Tutar', flex: 2, sortable: true, dataIndex: 'tutar', field: {xtype: 'textfield'} }
];
this.dockedItems = [{
xtype: 'toolbar',
items: [{
text: 'Ekle',
id:'addNewCheck',
iconCls: 'icon-add',
},'-',{
id: 'deleteCheck',
text: 'Sil',
iconCls: 'icon-delete',
disabled: true,
}]
}];
this.store = 'BOM.store.PaketStore';
rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
clicksToMoveEditor: 1,
autoCancel: false
});
this.plugins = rowEditing,
this.callParent(arguments);
},
returnRowEdit: function () {
console.log("row editing...");
return rowEditing;
}
});
var rowEditing;
Try:
this.store = Ext.create('BOM.store.PaketStore');
instead of:
this.store = 'BOM.store.PaketStore';
http://jsfiddle.net/qzMb7/1/
It works when I add ".getView()" like
this.getPaketWindow().getView().getStore().insert(0, new BOM.model.PaketModel())
However, I still do not understand. Both reaches the same store when I add records manually I can see them in the store.data but it is visible only if I include .getView() part
I am working with the mvc nested-loading example: http://docs.sencha.com/ext-js/4-1/#!/example/app/nested-loading/nested-loading.html. While trying to build my own project out of this I ran in to a problem trying to render the data in my content view. For some reason the templated data is not loading. I have a feeling it's something to do with xtype: 'component'. I changed it to panel and I seen the border but still no data. I also included the app.jsb3 file and that does not work either. I can see the data listed in the bind method but gets added.
My json data:
[
{id: 1, name: 'This is page one', url: 'http://my.test.com/test.html'},
{id: 2, name: 'This is page two', url: 'http://my.test.com/test2.html'}
]
My Model:
Ext.define('TST.model.Projects', {
extend: 'Ext.data.Model',
fields: ['id', 'name', 'url']
});
My Store:
Ext.define('TST.store.Projects', {
extend: 'Ext.data.Store',
model: 'RLA.model.Projects',
autoLoad: true,
proxy: {
type: 'ajax',
url : 'resources/json/projects.json'
}
});
My Controller:
Ext.define('TST.controller.Menu', {
extend: 'Ext.app.Controller',
stores: ['Projects'],
models: ['Projects'],
refs: [
{ref: 'mainLeftMenu', selector: 'mainleftmenu'},
{ref: 'mainContent', selector: 'maincontent'}
],
init: function() {
this.control({
'mainleftmenu': { selectionchange: this.onLeftMenuChange },
});
this.getProjectsStore().on({
scope: this,
load : this.onProjectsStoreLoad
});
},
onProjectsStoreLoad: function(store, records) {
Ext.defer(function() {
if (records.length) {
var record = records[0];
this.getMainLeftMenu().getSelectionModel().select(record);
console.log("iamhere");
}
}, 500, this);
},
/* load the project menu items */
onLaunch: function() {
this.getMainLeftMenu().bindStore(this.getProjectsStore());
},
/* update content on new selection */
onLeftMenuChange: function (view, records) {
if (records.length) {
this.showContent(records[0]);
}
},
showContent: function(record) {
this.getMainContent().bind(record);
}
});
Here is my leftContent view:
Ext.define('TST.view.main.LeftMenu', {
extend: 'Ext.view.View',
alias: 'widget.mainleftmenu',
initComponent: function() {
Ext.apply(this, {
id: 'leftmenu',
dock: 'left',
width: 200,
border: true,
cls: 'leftMenu',
selModel: {
deselectOnContainerClick: false
},
itemSelector: '.items',
tpl: [
'<div class="menuTitle">Projects</div>',
'<tpl for=".">',
'<div class="items">{name}</div>',
'</tpl>'
]
});
this.callParent(arguments);
}
});
Here is my content view:
Ext.define('TST.view.main.Content', {
extend: 'Ext.panel.Panel',
alias: 'widget.maincontent',
initComponent: function() {
Ext.apply(this, {
cls: 'content',
flex: 2,
border: false,
autoScroll: true,
layout: {
type: 'hbox',
align: 'middle',
pack: 'center',
availableSpaceOffset: Ext.getScrollbarSize().width
},
items: [
{
xtype: 'component', // think it has something to do with this?
itemId: 'contentCt',
width: 500,
height: 200,
border: 2,
tpl: [
'<div class="url">{url}</div>'
]
}]
});
this.callParent(arguments);
},
bind: function(record) {
this.child('#contentCt').update(record.getData());
}
});
And firebug shows that record.getData() returns:
object { id=1, name="This is page one", url="http://my.test.com/test.html"}
You will need to post more code, since this works without issue:
Ext.define('TST.view.main.Content', {
extend: 'Ext.panel.Panel',
alias: 'widget.maincontent',
initComponent: function() {
Ext.apply(this, {
cls: 'content',
flex: 2,
border: false,
autoScroll: true,
layout: {
type: 'hbox',
align: 'middle',
pack: 'center',
availableSpaceOffset: Ext.getScrollbarSize().width
},
items: [{
xtype: 'component', // think it has something to do with this?
itemId: 'contentCt',
width: 500,
height: 200,
border: 2,
tpl: ['<div class="url">{url}</div>']
}]
});
this.callParent(arguments);
},
bind: function(data) {
this.child('#contentCt').update(data);
}
});
Ext.onReady(function(){
var p = new TST.view.main.Content({
width: 500,
height: 200,
renderTo: document.body
});
p.bind({
url: 'foo'
});
});
I'm having a dumb problem and I would like you to give me a hand.Thanks in advance.
The situatios is as follows: I have 2 wiews (both created with sencha architect 2.0), one for login, and another for general purposes. And I would like to load the second view on successful response when trying to log in, this is, after any successful login. The main problem is that I've tried with Ex.create, Ext.Viewport.add, Ext.Viewport.setActiveItem, but I can't manage to make the second view to appear on screen, the login screen just keeps there and the app does not load the other view. Another thing, I don't have to use a navigation view for this.
Here is the code of my controller, from which I want to load my second view. And as you'll see, I even created a reference of the view, which has autoCreate enabled and has that ID "mainTabPanel":
Ext.define('MyApp.controller.Login', {
extend: 'Ext.app.Controller',
config: {
refs: {
loginButton: {
selector: '#login',
xtype: 'button'
},
username: {
selector: '#user',
xtype: 'textfield'
},
password: {
selector: '#pass',
xtype: 'passwordfield'
},
mainTabPanel: {
selector: '#mainTabPanel',
xtype: 'tabpanel',
autoCreate: true
}
},
control: {
"loginButton": {
tap: 'onLoginButtonTap'
}
}
},
onLoginButtonTap: function(button, e, options) {
Ext.Ajax.request({
url: '../../backend/auth.php',
method: 'POST',
params: {
user: this.getUsername().getValue(),
pass: this.getPassword().getValue()
},
success: function(response) {
var json = Ext.decode(response.responseText);
if (json.type == 'success') {
// LOAD THE DAMN SECOND VIEW HERE!
//var paneltab = Ext.create('MyApp.view.MainTabPanel');
//Ext.Viewport.add(paneltab);
//Ext.Viewport.setActiveItem(this.getMainTabPanel());
} else {
alert(json.value);
}
},
failure: function(response) {
alert('The request failed!');
}
});
}
});
And here is the code of my login view:
Ext.define('MyApp.view.LoginForm', {
extend: 'Ext.form.Panel',
config: {
id: 'loginForm',
ui: 'light',
items: [
{
xtype: 'fieldset',
ui: 'light',
title: 'Log into the system',
items: [
{
xtype: 'textfield',
id: 'user',
label: 'User',
name: 'user'
},
{
xtype: 'passwordfield',
id: 'pass',
label: 'Pass',
name: 'pass'
}
]
},
{
xtype: 'button',
id: 'login',
ui: 'confirm',
text: 'Login'
}
]
}
});
And finally, the code of the view I want to load. This view loads normally if I set it as the Initial View, but does not load when a successful login occurs:
Ext.define('MyApp.view.MainTabPanel', {
extend: 'Ext.tab.Panel',
config: {
id: 'mainTabPanel',
layout: {
animation: 'slide',
type: 'card'
},
items: [
{
xtype: 'container',
layout: {
type: 'vbox'
},
title: 'Tab 1',
iconCls: 'time',
items: [
{
xtype: 'titlebar',
docked: 'top',
title: 'General Report',
items: [
{
xtype: 'button',
iconCls: 'refresh',
iconMask: true,
text: '',
align: 'right'
}
]
},
{
xtype: 'container',
height: 138,
flex: 1,
items: [
{
xtype: 'datepickerfield',
label: 'From',
placeHolder: 'mm/dd/yyyy'
},
{
xtype: 'datepickerfield',
label: 'To',
placeHolder: 'mm/dd/yyyy'
},
{
xtype: 'numberfield',
label: 'Hours'
}
]
},
{
xtype: 'dataview',
ui: 'dark',
itemTpl: [
'<div style="height:50px; background-color: white; margin-bottom: 1px;">',
' <span style="color: #0000FF">{user}</span>',
' <span>{description}</span>',
'</div>'
],
store: 'hoursStore',
flex: 1
}
]
},
{
xtype: 'container',
title: 'Tab 2',
iconCls: 'maps'
},
{
xtype: 'container',
title: 'Tab 3',
iconCls: 'favorites'
}
],
tabBar: {
docked: 'bottom'
}
}
});
Please, I need help... :)
I'm stuck here for like 3 days now and can't figure out what the problem is. Thank you.
Could you post your app.js too?
I'm trying your code here and it load the view as expected. Here is my app.js:
Ext.application({
name: 'MyApp',
requires: [
'Ext.MessageBox'
],
controllers: ['Login'],
views: ['LoginForm','MainTabPanel'],
icon: {
'57': 'resources/icons/Icon.png',
'72': 'resources/icons/Icon~ipad.png',
'114': 'resources/icons/Icon#2x.png',
'144': 'resources/icons/Icon~ipad#2x.png'
},
isIconPrecomposed: true,
startupImage: {
'320x460': 'resources/startup/320x460.jpg',
'640x920': 'resources/startup/640x920.png',
'768x1004': 'resources/startup/768x1004.png',
'748x1024': 'resources/startup/748x1024.png',
'1536x2008': 'resources/startup/1536x2008.png',
'1496x2048': 'resources/startup/1496x2048.png'
},
launch: function() {
// Destroy the #appLoadingIndicator element
Ext.fly('appLoadingIndicator').destroy();
// Initialize the main view
//Ext.Viewport.add(Ext.create('MyApp.view.Main'));
Ext.Viewport.add(Ext.create('MyApp.view.LoginForm'));
},
onUpdated: function() {
Ext.Msg.confirm(
"Application Update",
"This application has just successfully been updated to the latest version. Reload now?",
function(buttonId) {
if (buttonId === 'yes') {
window.location.reload();
}
}
);
}
});
Destroy the login panel, You don't really need it anymore...
success: function(response) {
var json = Ext.decode(response.responseText);
if (json.type == 'success') {
// LOAD THE DAMN SECOND VIEW HERE!
var paneltab = Ext.create('MyApp.view.MainTabPanel');
Ext.getCmp('loginForm').destroy();
Ext.Viewport.add(paneltab);
} else {
alert(json.value);
}
},
There are several problems here:
Your autoCreate rule uses an xtype: 'tabpanel' which will only ever create a vanilla Ext.TabPanel with no items in it. You'd need to assign an xtype attribute to your MyApp.view.MainTabPanel like xtype: 'mainTabPanel' and then use that xtype value in your autoCreate rule.
This then explains why this code won't work since this.getMainTabPanel() will return the wrong object. Simpler would be to just use Ext.Viewport.setActiveItem(paneltab).
In general, you usually don't want assign an id to a view (id: 'mainTabPanel'). Safer to just use an xtype to fetch it. Although you don't need it in this case, avoiding global id's allows you to create multiple instances of a view (or any other class type).