Multi model data binding - sapui5

OK, I have a great confusion about data binding in SAPUI5.
After reading this and this info I still don't know why my code doesn't work.
I define the data and the model in onInit function of the controller:
var oControlsData = {
controls: [
{control: [
{title: "Input Filed"},
{visible: true},
{iconUrl: "sap-icon://edit"}
]},
{control :[
{title: "Combo Box"},
{visible: true},
{iconUrl: "sap-icon://edit"}
]},
{control :[
{title: "Radio Button"},
{visible: true},
{iconUrl: "sap-icon://bo-strategy-management"}
]}
]
};
var oControlsModel = new sap.ui.model.json.JSONModel(oControlsData);
sap.ui.getCore().setModel(oControlsModel, "controls");
Then, in onAfterRendering function I try to bind data from the model to the sap.m.List control:
var oList = sap.ui.getCore().byId("controlsList");
var oListTemplate = new sap.m.StandardListItem({
title: "{title}",
visible: "{visible}",
icon: "{iconUrl}"
});
oList.bindItems({
path: "controls>/controls/control",
template: oListTemplate
});
It seems to me the path is correct, but I still get "No Data" in the control.
What went wrong here?
If I miss something big in understanding data binding process, please give a brief explanation, because every time I have to play with data binding I wonder if it work or not.

The path in bindItems should be the path to the list of Items.
So in your case controls>/controls.
The paths of the properties of the ListItem should then be relative to each object in the list.
sap.ui.getCore().byId("controlsList").bindItems({
path: "controls>/controls",
template: new sap.m.StandardListItem({
title: "{controls>control/0/title}",
visible: "{controls>control/0/visible}",
icon: "{controls>control/0/iconUrl}"
})
});
You need the control/0/ because of your inner array. If you have control over the structure of your data, I would recommend to remove that.
var oControlsData = {
controls: [
{
title: "Input Filed",
visible: true,
iconUrl: "sap-icon://edit"
},
...
]
};
On a sidenote, why are you doing databinding in onAfterRendering?
This should be done in the View. For example in XML:
<List items="{controls>/controls}">
<StandardListItem title="{controls>control/0/title}" visible="{controls>control/0/visible}" icon="{controls>control/0/iconUrl}" />
</List>

Related

ExtJs 6 Store not visible in Grid

I'm trying to port existing application from ExtJs 4.2.1 to 6.0.1
The problem that in debugger I see that grid has 'ext-empty-store' store instead of 'store.accounting.Quota'
I can load the store directly in panel activation listener by doing
var store = Ext.data.StoreManager.lookup('QuotaKPI.store.accounting.Quota');
store.load();
In firebug I see request and perfect json in response but nothing appears in the grid
Here are code snippets
app/store/accounting/Quota.js
Ext.define('QuotaKPI.store.accounting.Quota', {
extend: 'Ext.data.JsonStore',
model: 'QuotaKPI.model.accounting.QuotaModel',
alias: 'store.accounting.Quota',
storeId: 'QuotaKPI.store.accounting.Quota',
autoLoad: false,
proxy: {
...
}
});
app/view/accounting/QuotaGrid.js
Ext.define('QuotaKPI.view.accounting.QuotaGrid', {
extend: 'Ext.grid.Panel'
,xtype: 'QuotaGrid'
,store: Ext.data.StoreManager.lookup('QuotaKPI.store.accounting.Quota')
,columns: [
...
]
,dockedItems : [
,{xtype: 'pagingtoolbar',
dock:'bottom',
store: Ext.data.StoreManager.lookup('QuotaKPI.store.accounting.Quota'),
displayInfo: true,
displayMsg: 'Displaying Quota Details {0} - {1} of {2}',
emptyMsg: "No Quota to display"
}
]
,initComponent: function() {
this.callParent(arguments);
}
});
Store, model and grid declared in controller
Ext.define('QuotaKPI.controller.accounting.AccountingController', {
extend: 'Ext.app.Controller',
stores: ['accounting.Quota'],
models: ['accounting.QuotaModel'],
views: ['accounting.QuotaGrid']
...
And controller itself listed in app.js
Ext.application({
name: 'QuotaKPI',
controllers: [
'accounting.AccountingController'
],
init: function(app){
},
autoCreateViewport: true
});
Any help, please?
I know storeId doesn't accept some character (for example "-"), I don't know for dot... in any case I suggest to make it simple.
Try "myStoreId"
In addition you can try:
Ext.define('QuotaKPI.view.accounting.QuotaGrid', {
extend: 'Ext.grid.Panel'
,xtype: 'QuotaGrid'
,store: "myStoreId",
,columns: [
...
]
,dockedItems : [
,{xtype: 'pagingtoolbar',
dock:'bottom',
store: "myStoreId",
displayInfo: true,
displayMsg: 'Displaying Quota Details {0} - {1} of {2}',
emptyMsg: "No Quota to display"
}
]
,initComponent: function() {
this.callParent(arguments);
}
});
In addition I suggest to ensure you have a proper schema configuration (see http://docs.sencha.com/extjs/6.0/6.0.1-classic/#!/api/Ext.data.schema.Schema)
And then, you could try also with ViewModel instead of storeId (see http://docs.sencha.com/extjs/5.0/application_architecture/view_models_data_binding.html)
Don't hesitate to do a https://fiddle.sencha.com/#home
Good Luck!
Transition is not easy...

Extending own classes

i am develop an app with several windows. Many windows are similar, so i think write a superclass, and extend it.
I have, the superclass:
Ext.define('AM.view.ui.DecoratorAbstract',{
extend: 'Ext.window.Window',
alias: 'widget.decoratorAbstract',
initComponent: function(){
this.title = this.aTitle;
this.resizable = this.cfg[0];
this.closable = this.cfg[1];
this.minimizable = this.cfg[2];
//this.items = this.anItem;
this.callParent(arguments);
}
});
And the subclass:
Ext.define('AM.view.ui.DecoratorForm',{
extend: 'AM.view.ui.DecoratorAbstract',
alias: 'widget.decoratorForm',
initComponent: function(){
this.callParent();
this.buttons = [
{ text:'Guardar', action:'save', iconCls: 'ico-save' },
{ text:'Cancelar', action:'cancel', iconCls: 'ico-cancel' }
];
}
});
Both classes are included in the Controller:
Ext.define('AM.controller.Ui',{
extend: 'Ext.app.Controller',
views: [
'ui.Toolbar',
'ui.Statusbar',
'ui.AlertErr',
'ui.AlertOk',
'ui.AlertWar',
'ui.AlertDelete',
'ui.AlertUndelete',
'ui.DecoratorAbstract',
'ui.DecoratorForm',
'ui.DecoratorGrid'
],
model: [],
store: [],
});
From the Firebug js console i create the subclass:
Ext.create('AM.view.ui.DecoratorForm',{cfg:[true,true,true],aTitle: 'Title'}).show();
The window is showed but, not the buttons.
Any ideas ?.
There are a few things here... First, move this.callParent() to the end of your initComponent. This is because the initComponent further up the inheritance does something with this.buttons, and you are missing out on that by calling callParent before setting the buttons.
Next, you really shouldn't use this cfg thing that you are passing in. Just pass in the config parameters that you want to use, and they will be available:
Ext.define('AM.view.ui.DecoratorAbstract',{
extend: 'Ext.window.Window',
alias: 'widget.decoratorAbstract',
initComponent: function(){
this.callParent(arguments);
}
});
Ext.define('AM.view.ui.DecoratorForm',{
extend: 'AM.view.ui.DecoratorAbstract',
alias: 'widget.decoratorForm',
initComponent: function(){
this.buttons = [
{ text:'Guardar', action:'save', iconCls: 'ico-save' },
{ text:'Cancelar', action:'cancel', iconCls: 'ico-cancel' }
];
this.callParent();
}
});
//to instantiate:
Ext.create('AM.view.ui.DecoratorForm',{
resizable: true,
closable: true,
minimizable: true,
title: 'Title'
}).show();
Anytime you are trying to "trick" the component by using something like that cfg array, you should probably re-think what you're doing and see if there's a smarter way.
One other thing you should look into using is Ext.apply(). It will save a lot of bytes by changing what you had before into something like this:
Ext.apply(this, {
title: 'my title',
resizable: cfg[0],
closable: cfg[1],
///...etc...
})
What if you move
this.callParent(arguments);
To the end of initComponent in your DecoratorForm.

Sencha ExtJS RESTful grid example confusion

I am very confused by the Sencha documentation for ExtJS. The documentation begins with a Getting Started guide which highlights and illustrates the importance on a suitable structure for the classes and source code of your application. But the provided examples then break all the conventions laid down by the Getting Started guide. Instead of code being broken down into appropriate Model, Store, View, etc. class files the examples are provided as a single file with example source code which is not easily re-usable in separate source files.
I started by following the Portal example (http://docs.sencha.com/ext-js/4-1/#!/example/portal/portal.html) as this is the sort of application I want to create. I wanted to enhance the Portal example and add in a screen which would display a grid and use a RESTful web service as the data backend. I have created the backend I just want to create the front-end. So I looked at the RESTful example (http://docs.sencha.com/ext-js/4-1/#!/example/restful/restful.html)
I have tried to copy the RESTful example into the recommended pattern of seperate classes e.g. Model, Store, View:
Model:
Ext.define('MyLodge.model.Member', {
extend: 'Ext.data.Model',
fields: [
{name: 'name', type: 'string'},
{name: 'email', type: 'string'},
{name: 'href', type: 'string'}
]
});
Store:
Ext.require('MyLodge.model.Member');
Ext.define('MyLodge.store.Members', {
autoLoad: true,
autoSync: true,
model: 'MyLodge.model.Member',
proxy: {
type: 'rest',
url: 'http://localhost:8888/rest/memberapi/members' ,
reader: {
type: 'json',
root: 'data'
},
writer: {
type: 'json'
}
},
listeners: {
write: function(store, operation){
var record = operation.getRecords()[0],
name = Ext.String.capitalize(operation.action),
verb;
if (name == 'Destroy' ) {
record = operation.records[0];
verb = 'Destroyed';
} else {
verb = name + 'd';
}
Ext.example.msg(name, Ext.String.format( "{0} member: {1}", verb, record.getId()));
}
}
});
View:
Ext.define('MyLodge.view.content.MemberGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.membergrid',
initComponent: function(){
var store = Ext.create('MyLodge.store.Members' );
Ext.apply( this, {
height: this.height,
store: store,
stripeRows: true,
columnLines: true,
columns: [{
id : 'name',
text : 'Name',
flex: 1,
sortable : true,
dataIndex: 'name'
},{
text : 'E-Mail',
width : 150,
sortable : true,
dataIndex: 'email'
},{
text : 'Href',
width : 200,
sortable : true,
dataIndex: 'href'
}],
dockedItems: [{
xtype: 'toolbar',
items: [{
text: 'Add',
iconCls: 'icon-add',
handler: function(){
// empty record
store.insert(0, new MyLodge.model.Member());
rowEditing.startEdit(0, 0);
}
}, '-', {
itemId: 'delete',
text: 'Delete',
iconCls: 'icon-delete',
disabled: true,
handler: function(){
var selection = grid.getView().getSelectionModel().getSelection()[0];
if (selection) {
store.remove(selection);
}
}
}]
}]
});
this.callParent(arguments);
}
});
But I am not sure where to put the code to control the grid row selection and enable the Delete button:
grid.getSelectionModel().on('selectionchange', function(selModel, selections){
grid.down('#delete').setDisabled(selections.length === 0);
});
Also when I press the Add button I get the following error:
Uncaught TypeError: Object [object Object] has no method 'insert'.
Any help would be appreciated.
You are having scoping issues. Basically the variable store is defined only in the initComponent function and therefore of local function scope.
Your handler function has it's own scope. It is firing in the scope of the toolbar button. So if you say this in the handler it would refer to the button. Hence you can say this.up('panel').store - and that gives you the correct reference to the store backing your grid panel.
Another advice is not to implement everything at once. Write a little bit to see if it works and then add to it little by little.
RE: the docs examples, I agree that it's frustrating, but there's not many options. Having a fully-MVC-style implementation of each example would not only be onerous to produce, but would also probably make point of the example get lost in the structure.
RE: your question about the where to "put" the code to control the grid, I would recommend setting up a controller with listeners for the events on the grid in the control() section. This will let you decouple the handling of the events that are fired by your grid from the view itself.

Sencha touch 2 filterby() not updating records

I have a nested list on one of the pages of a Tabbed Panel app that is pulling data from "offices.json"
I should like to be able to filter this list when a user clicks on a toolbar button. However my filterBy() function doesn't update the store and the list of offices I can see, even though I can see in the console it is iterating the records and finding a match. What am I doing wrong?
(And yes I have tried doing s.load() both before and after the filterBy to no avail!)
toolbar:{
items:[{
text: 'Near you',
id: 'btnNearYou',
xtype: 'button',
handler: function() {
s = Ext.StoreMgr.get('offices');
s._proxy._url = 'officesFLAT.json';
console.log("trying to filter");
s.filterBy(function(record) {
var search = new RegExp("Altrincham", 'i');
if(record.get('text').match(search)){
console.log("did Match");
return true;
}else {
console.log("didnt Match");
return false;
}
});
s.load();
}
}]
For the record I'm defining my store like so:
store: {
type: 'tree',
model: 'ListItem',
id: 'offices',
defaultRootProperty: 'items',
proxy: {
type: 'ajax',
root: {},
url: 'offices.json',
reader: {
type: 'json',
rootProperty: 'items'
}
}
}
No need to recreate the regex each time, cache it outside.
You can simplify the code a lot (see below).
Why are you calling load directly after? That's going to send it to the server and it will just retrieve the same dataset.
toolbar: {
items: [{
text: 'Near you',
id: 'btnNearYou',
xtype: 'button',
handler: function() {
s = Ext.StoreMgr.get('offices');
s._proxy._url = 'officesFLAT.json';
var search = /Altrincham/i;
s.filterBy(function(record) {
return !!record.get('text').match(search);
});
}
}]
}

Simple login form with SenchaTouch

Just diving into SenchaTouch which seems very promising.
I'm building my first application, a simple login form check source http://pastebin.com/8Zddr9cj
I'm looking for a way to do the following things :
Display 'nice' error message when the login/password is wrong. Can be in red to replace the 'Please enter your credentials); i don't know how to access this property.
If login success, close the form and load the application (probably another js file).
Quite simple, but i'm a newbie to this,
1) Fieldset has a method called setInstructions which you can call to update the instructions. So, you could specify an id configuration in your field set, then use that later on when you want to update the instructions.
...
items: [
{
xtype: 'fieldset',
id: 'fieldset',
title: 'Login',
instructions: 'Please enter your credentials',
defaults: {
required: true,
labelAlign: 'left',
labelWidth: '40%'
},
items: [
{
xtype: 'emailfield',
name : 'email',
label: 'Email',
placeHolder: 'your#email.com',
useClearIcon: true
}, {
xtype: 'passwordfield',
name : 'password',
label: 'Password',
useClearIcon: false
}]
}
],
...
//wherever you want to update the instructions
var fieldset = Ext.getCmp('fieldset');
fieldset.setInstructions('My new instructions!');
2) Here is a simple demo of this:
//create a panel, which is full screen, and will contain your form, and another item
//which you want to show at some point
var wrapper = new Ext.Panel({
fullscreen: true,
layout: 'card',
//my two items
items: [
form,
{
xtype: 'panel',
html: 'my second panel, which is not visible on render.'
}
]
});
//change the active card/item, when you need to
wrapper.setActiveItem(1); //starts at 0
Make sure you remove fullscreen from your form, as it is no longer fullscreen (this wrapper panel is).