I don't understand the usage of alias vs id vs itemId config properties in ExtJS
app/view/foo.js
Ext.define('app.view.foo', {
...
alias: 'widget.foo', // 1
id: 'foo', // or 2
...
});
app/controller/goo.js
Ext.define('app.controller.goo', {
...
views: ['foo', ...],
init: function() {
this.control({
'foo': {...}, // 1
'#foo': {...} // or 2
...
});
...
},
...
});
With alias I can use xtype easily.. but what advantage do I gain by setting id's to my views?
An alias is set on the class definition, using Ext.define, and is what is used for the xtype when creating instances (you seem to have the hang of this). An id should be set on a class instance, e.g. when using Ext.create. This just serves as a unique identifier for a particular instance of a component.
FWIW, id should be used sparingly. You are much better served using itemId and referencing components relatively using that, rather than globally with an id.
Related
I'm working on Ext JS MVC app, that needs to be localized. Trying to reproduce official docs (http://docs.sencha.com/extjs/6.2.0/guides/core_concepts/localization.html).
Locale file load correctly.
Console message:
[W] Overriding existing mapping: 'viewmodel.users' From
'clt.locale.en.view.users.UsersModel' to 'clt.view.users.UsersModel'.
Is this intentional?
but overriding values don't display (they should be grid columns headers.
Model looks like this:
Ext.define('clt.view.users.UsersModel', {
extend: 'Ext.app.ViewModel',
requires:[
// something
],
// singleton: true,
data: {
key1: 'value1',
key2: 'value2',
keyN: 'valueN',
},
stores: {
// something
}
});
Values binding to view like this:
bind: { text: '{key1}' }
If I make this model singleton, localization starts working (grid headers displayed localized values), but grid data is empty.
So, what's the problem? Help me understanding it.
Update. Problem resolved. I found thread on Sencha forum with solution: add localized elements in config object in localization file. Example:
Ext.define('clt.locale.en.view.users.UsersModel', {
override: 'clt.view.users.UsersModel',
config: {
data: {
key1: 'value1',
// some other keys
}
}
});
Warnings are not a good sign. In your case, you don't apply an override like you should. The message
[W] Overriding existing mapping: 'viewmodel.users' From 'clt.locale.en.view.users.UsersModel' to 'clt.view.users.UsersModel'. Is this intentional?
says that first, clt.locale.en.view.users.UsersModel (your localized version) is loaded, and after that, clt.view.users.UsersModel (non-localized version) is loaded and completely replaces the localized version.
What you want to do is the following:
Ext.define('clt.view.users.UsersModel', {
extend: 'Ext.app.ViewModel', // <- EXTEND ViewModel class
// define your locale-agnostic user model here
});
Ext.define('clt.locale.en.view.users.UsersModel', {
override: 'clt.view.users.UsersModel', // <- OVERRIDE implementation in the overridden class
// define your localized properties here.
});
This should remove the warning. Then you can instantiate the UsersModel
Ext.create('clt.view.users.UsersModel', {
but you get a localized version of it.
For testing purposes, I made 2 views. One that requires the other view. Now I want people to be able to open components multiple times as a tab, so I obviously have to assign unique ID's to each tab and element inside of the component, right?
My question is, how can I access one of the root properties in a view from say the docketItems object?
In the code below, it will cause an undefined this.varindex error at id: 'accountSearchField' + this.varindex,. varindex is dynamically assigned from the other component. (I hardcoded it in the example code below)
Note that I will not know the exact ID, so I can not use something such as Ext.getCmp. I could make use of Ext.ComponentQuery.query('searchAccount') but perhaps there is a better way to do this?
Listed below is a portion of the code that is required by my main component.
Ext.define('cp.views.search.Account', {
extend: 'Ext.panel.Panel',
xtype: 'searchAccount',
varindex: "uniqueid_assigned_by_main_component",
listeners: {
beforerender: function(){
this.id = 'panelSearchAccount' + this.varindex
}
},
items: [
{
xtype: 'grid',
store: {
type: 'Account'
},
id: 'searchAccount' + this.varindex,
columns: [
///
],
dockedItems: [
{
xtype: 'fieldset',
items: [
{
id: 'accountSearchField' + this.varindex,
xtype: 'searchfield'
}
]
}]
}
]
});
Ext will generate IDs for DOM elements and ensure they are unique to the page, so it is unusual to use the id attribute for referencing components. There are two configuration properties with this purpose, itemId and reference.
There are multiple methods that can be used to acquire a component by itemId from a parent container or globally.
Ext.container.Container.getComponent
Ext.container.Container.query
Ext.container.Container.down
Ext.container.Container.child
Ext.ComponentQuery.query
Additionally, components can be acquired by Ext.app.Controllers using the refs configuration. These methods take a selector string. The selector for an itemId is the itemId prefixed with '#' (e.g. '#itemId'). You do not specify the '#' when configuring a component with an itemId.
Introduced along with support for MVVM in Ext JS 5, the reference identifier is unique to the component's container or ViewController. A component can be acquired by reference from components or ViewControllers using lookupReference. The lookupReference method takes only references as input and therefore, does not require a prefix.
If you want to be able to reference a component associated with a particular model instance (account), you can define the component class with an alias and configure the reference or itemId when you add each instance to the container.
for (var i = 0, ilen = accountModels.length; i < ilen; ++i) {
container.add({
xtype: 'accountpanel',
reference: accountModel[i].get('accountNumber')
});
}
In this example, an account's associated panel can be acquired later on using the account number.
var accountPanel = this.lookupReference(accountModel.get('accountNumber'));
Actually I may have figured it out. I simply moved the items inside of the beforerender event and made use of the this.add() function.
I have a view defined as following:
Ext.define('senchaTest.view.ModelDetailsView', {
extend: 'Ext.Panel',
requires: [
],
xtype: 'modeldetailsview',
config: {
modelName: null
layout: 'vbox',
items: [
{
xtype: 'label',
itemId: 'modelinformationview-name-label'
}]
},
updateModelName: function(modelName) {
var components = Ext.ComponentQuery.query('.modelinformationview #modelinformationview-name-label');
if (components && components.length > 0) {
components[0].setHtml(modelName);
};
}
});
I want to reuse this view in a tab panel. I have two tabs in tab panel. Each will have an instance of the above defined view. However, each will have different data and role.
When I try to set the config values of instances of these views, only one config is used. I understand that this happens because Ext.ComponentQuery queries the same component (For example, '.modelinformationview #modelinformationview-name-label'). It returns two components from each instance of the created views, I pick the first one and use that. Hence only one view is used always.
I want to know how to reuse defined views like this. I have some idea that Controllers can play a role in achieving this. But I haven't yet figured the best way to do it. Please help.
Thanks.
this is an instance of the right modeldetailsview in the updateModelName() function, hence it is as simple as:
updateModelName: function(modelName) {
var component = this.down('[itemId=modelinformationview-name-label]');
component.setHtml(modelName);
}
[EDIT]
I made this example to show you how to reuse components identifying them by itemId: https://fiddle.sencha.com/#fiddle/45o.
I defined the Fiddle.view.Main with two instances of Fiddle.view.Reusable, then in the initialize event of the Main view I get a reference to Main view, and from it I use Ext.Container.getComponent() to get the instances of the components by itemId.
itemId is just a way of identifying an instance of a component without polluting the global id space, and you can use it both to get an item in a container with Ext.Container.getComponent('foo'); like I did in my example, or more generally with componentQuery('[itemId=foo]'); like I did to answer your question.
A simple example :
Ext.define('App.view.Mygrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.myGrid',
itemId: 'myGrid',
}
Then later you can add this by adding it to the items property of a parent like this :
items:
[{ xtype: 'myGrid' }]
Note that you don't need the itemId in this case. The alias property makes it so that you can instantiate views using the alias as an xtype.
I have a simple data model that looks something like this (actual code below):
model Game:
fields: id, team_1_id, team_2_id
model GameScore:
fields: id, game_id, team_1_score, team_2_score, is_final, submission_date
model SpiritScore:
fields: id, game_id, team_1_score, team_2_score
What I want seems simple. I already have code that loads Games and GameScores in bulk. I have a 'Game' instance in hand, and can call gameScores(). And I get a store, but it's empty. I have code that will dynamically load it, by placing the store into the model's hasMany definition. But what I would really like is some way to bind the Game.gameScores() call to the my existing GameScores store. Even if it used a normal filter underneath, that gives me a single record that I can bind and use in a view. (Important note: the data does not come in nested form.)
This leads to my second question. Game:GameScores is 1:many, but I only ever display the most recent one (from live score reporting). What is the general approach here? I can also manually build a filter from the game_id, but I can only bind 1 record to a view, so I don't see how I can bring that other information into a view, short of a proper hasMany relationship. Is there another way?
Any and all advice, including telling me to RTFM (with a link to the relevant manual) would be greatly appreciated! I've been pulling my hair out on this (pro bono side project) for the last week.
Cheers!
b
Ext.define('TouchMill.model.Game', {
extend: 'Ext.data.Model',
config: {
fields: [ 'id', 'team_1_id', 'team_2_id' ],
hasMany: {
model: 'TouchMill.model.GameScore',
name: 'gameScores',
},
},
});
Ext.define('TouchMill.model.GameScore', {
extend: 'Ext.data.Model',
config: {
fields: [ 'id', 'game_id', 'team_1_score', 'team_2_score', 'is_final', 'submission_date', ],
},
// belongsTo necessary? Don't think so unless I want parent func?
});
Ext.define('TouchMill.model.SpiritScore', {
extend: 'Ext.data.Model',
config: {
fields: [ 'id', 'game_id', 'team_1_score', 'team_2_score', ],
},
},
I've never used touch, so I'm speaking about Ext4 here (4.2 to be precise)... And, your model definitions seem a bit broken to me (is that working with touch?). But whatever, you'll get the general idea. If my code don't work in touch, please try with Ext4.
Also, I understood that you're loading all your scores at once. If that's not the case, my solution will need to be adapted...
So, my general reasoning is the following: if you've loaded all your scores in memory, then why not use a memory proxy that uses the score store's data as the data source for the store generated for the association? I tried that and, quite to my surprise, it worked without a glitch.
To understand this, you need to know that a proxy is an independant data source, that is a proxy can be shared between multiple stores without problem. On the other hand, a store is expected to be bound to a single view or task. For example, if you bind the same store to two different grids, then filtering the first grid will affect the second as well.
And while most proxies do not "contain" their data, memory proxy do. Here's a relevant excerpt of Ext.data.proxy.Memory#read method:
resultSet = operation.resultSet = me.getReader().read(me.data)
So, enough theory, here's the proof of concept (tested in this fiddle):
// I instantiate this proxy myself in order to have a reference available
var masterScoreProxy = Ext.create('Ext.data.proxy.Memory');
Ext.define('TouchMill.model.GameScore', {
extend: 'Ext.data.Model',
fields: [ 'id', 'game_id', 'team_1_score', 'team_2_score', 'is_final', 'submission_date' ],
// I've used a remote server to ensure this all works even asynchronously
proxy: {
// configure your own
}
});
Ext.define('TouchMill.model.Game', {
extend: 'Ext.data.Model'
,fields: [ 'id', 'team_1_id', 'team_2_id' ]
,hasMany: {
model: 'TouchMill.model.GameScore'
,name: 'gameScores'
// required in order to avoid Ext autogenerating it as 'touchmill.model.game_id'
,foreignKey: 'game_id'
// needed if we don't want to have to call gameRecord.gameScores().load()
,autoLoad: true
// first part of the magic: make the generated store use my own proxy
,storeConfig: {
proxy: masterScoreProxy
}
}
});
// Just mocking a store with two games
var gameStore = Ext.create('Ext.data.Store', {
model: 'TouchMill.model.Game'
,data: [{id: 1}, {id: 2}]
,proxy: 'memory'
});
// Creating the "master" score store (that will use the model's proxy)
var scoreStore = Ext.create('Ext.data.Store', {
model: 'TouchMill.model.GameScore'
// second part's in there
,listeners: {
load: function(store, records, success) {
if (success) {
// 1. replace the data of the generated association stores' proxy
// (I must say I'm quite surprised that I didn't had to extract the data of
// every records, nor to configure a reader and all for my shared proxy...
// But hey, that works!)
masterScoreProxy.data = records;
// 2. update already generated stores
// Alternatively, you could call gameRecord.gameScores().load() individually
// before each usage of gameRecord.gameStores()
gameStore.each(function(record) {
var childStore = record.gameScoresStore;
if (childStore) {
childStore.load();
}
});
}
}
}
});
// test first load
scoreStore.load({
callback: function(records, operation, success) {
if (success) {
// and here's to prove it
gameStore.each(function(record) {
record.gameScores().each(function(score) {
console.log('Game ' + record.id + ': ' + JSON.stringify(score.data, undefined, 2));
});
});
testRefreshedData();
}
}
});
function testRefreshedData() {
// test refreshing
scoreStore.load({
callback: function(records, operation, success) {
if (success) {
console.log('--- Scores have changed ---');
gameStore.each(function(record) {
record.gameScores().each(function(score) {
console.log('Game ' + record.id + ': ' + JSON.stringify(score.data, undefined, 2));
});
});
}
}
});
}
Regarding your other questions...
If you have a 1:n for Game:Score, you've got a 1:1 for Game:MostRecentScore... So, I'd try to use that.
As for the view, there should always be a way -- even if hackish -- to access data nested in your records. The way will depend on what you're calling view here... See, for example this question.
I'm trying to set up an Extensible Calendar Pro in my ExtJs 4.1 application, but I still get a name is undefined error.
EDIT:
I solved the original problem, but directly went in another.
Updated code:
Ext.define('ZeuS.view.panels.ZeusMainPanel',{
extend: 'Ext.panel.Panel',
id : 'zeusMainPanel',
alias : 'widget.zeus',
requires : [
'Extensible.Extensible',
'Extensible.calendar.CalendarPanel',
'Extensible.calendar.data.MemoryEventStore',
'Extensible.calendar.data.EventModel',
'Extensible.calendar.view.*'
],
autoShow : true,
layout : 'border',
border : false,
initComponent : function(){
this.items = [{
/*
* Some other Ext Elements
*/
}, {
region : 'east',
xtype : 'extensible.calendarpanel',
name : 'zeus-calendar',
width : 500,
eventStore: Ext.create('Extensible.calendar.data.EventStore', {
data: Ext.create('Extensible.calendar.data.EventModel',{
StartDate: '2101-01-12 12:00:00',
EndDate: '2101-01-12 13:30:00',
Title: 'My cool event',
Notes: 'Some notes'
})
})
}
];
this.callParent(arguments);
}
});
Now it loads all classes correctly when the Extensible singleton is included, but nothing works. I just have a white screen and no functions in the controller or anywhere else are called. When I remove it from the requires list it comes up with this error: Extensible.log is not a function
Do I use the plugin at all right?
Any advice?
Extensible.log is defined on the Extensible singleton, so it should always be available if your dependencies and includes are set up correctly. You really should post in the Extensible forums with additonal details (Ext version, Extensible version, script include markup) as this is basically a product support question.
EDIT: By the way, there is no such thing as Extensible.Extensible, which might be part of your problem. Also you cannot use wildcard requires statements for non-Sencha classes. You might try getting a basic example working first before trying to create a complex layout with it.