Why the refs to itemId cannot trigger the initialize event in Sencha Touch? - extjs

This is the controller code:
Ext.define('XXX.controller.XXX', {
extend: 'Ext.app.Controller',
config: {
views: ['CustomView','CarouselView'],
refs: {
custom: "carouselview #customid"
},
control: {
custom: {
initialize : function() {
alert("it's loading")
}
}
}
},
launch: function(){
Ext.Viewport.add(Ext.create('XXX.view.CustomView'));
console.log(this.getCustom()) // ——> This works, it is not undefined
}
});
and this is the carousel view code:
Ext.define('XXX.view.CarouselView', {
extend: 'Ext.Carousel',
xtype: 'carouselview',
defaults: {
styleHtmlContent: true
},
config:{
direction: 'horizontal',
items: [
{
xtype: 'customview',
itemId: 'customid'
}
]
}
});
Now it's the customview :
Ext.define('XXX.view.CustomView', {
extend: 'Ext.Panel',
xtype: 'customview',
config: {
tpl: XXX
}
});
in the controllers's launch function, it can log the right value, but the initialize event can't be triggered.
And if i change refs to { custom: "customview" }, the initialize event can be triggered.

IMHO you (and someone answered below) misunderstand the use of itemId config.
Here is the difference between id and itemId:
id is the global identifier of a component. It can be used directly as a selector in Ext.ComponentQuery class which refs uses behind the scene. So if you want something like "carouselview #customid", you have to use id instead of itemId.
itemId is the global identifier within a class from which the component derives from. For example, assume that you have an Ext.Button with itemId: "myCustomButton", then you can have access to it via this refs: button#myCustomButton (please note that there's no space between them). This way, Ext.ComponentQuery first looks for all components xtyped button, then find the instance with that itemId.
So, if you want to use some string as "first-class" selector, you will have to use id. If you want to use itemId, you may want to always include its xtype before the itemId. Therefore, 2 possible solutions are:
First solution (still use itemId): custom: "carouselview customview#customid"
Second solution: keep your refs, but change #customid from itemId to id
Hope this helps.

UPDATE:
Just figured out that you are trying to initialize on something that get's the itemId on initialize :) Sorry, took me some time.
Basically the fireEvent('initialize') has already been in the past when you are trying to listen to it in the controller.
Use the xtype to initialize or simply:
Ext.define('XXX.view.CustomView', {
extend: 'Ext.Panel',
xtype: 'customview',
config: {
tpl: XXX
},
initialize: function() { // good way to use initialize inside the view, as it belongs to the view and there is not user input handled
}
});
OR
Ext.define('XXX.controller.XXX', {
extend: 'Ext.app.Controller',
config: {
views: ['CustomView','CarouselView'],
refs: {
custom: ".carouselview .customview" // --> HERE use this
},
control: {
custom: {
initialize : function() {
alert("it's loading") // Yeah, now you are getting here
}
}
}
},
launch: function(){ // --> this will be the same as if you are placing it in app.js launch
Ext.Viewport.add(Ext.create('XXX.view.CustomView')); // --> here the initialize happends and this.getCustom() does not yet exists
console.log(this.getCustom()) // ——> here this.getCustom() exists
}
});

Related

How to bind correctly a formula with a store in Sencha ExtJs v6?

Here is a configuration for the formula:
formulas: {
//this binding with the store did not work :(
countDeactivatedVehicles: {
bind: {
bindTo: "{organizationCars}",
deep: true,
},
get: function (store) {
return store.query("isCarActive", false).getCount();
}
}
}
(currently now the count that we want is only displayed once initially meaning that on load it works ok)
When the models inside the store organizationCars have an attribute updated the binding does not work, the store is not alerted that its models have been updated.
What ideally should happen is when the model gets updated the event is propagated to the store so that the store knows that is changed. This way the binding would work (?) and the formula would get calculated.
Deepbinding, does not bind that deep.
Here is the answer to your question: Fiddle
I got it working in there.
But - personally - I would go with Theo's idea, because deep binding, is a lot of overhead.
I don't think this is actually possible using formulas, but you can do using events.
by listening to load datachanged and update events you can be notified of any changes to the store, from here you can do what you would do in a formula and manually set on the ViewModel.
This fiddle shows the solution best: https://fiddle.sencha.com/#view/editor&fiddle/1qvf
Store
Ext.define('Fiddle.Store', {
extend: 'Ext.data.Store',
alias: 'store.test',
listeners: {
load: 'storeUpdate',
update: 'storeUpdate',
datachanged: 'storeUpdate'
},
fields: [{
name: 'include',
type: 'bool'
}]
});
ViewModel
Ext.define('Fiddle.StoreBinderViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.storebinder',
stores: {
teststore: {
type: 'test'
}
},
data: {
includedTotal: 0
}
});
Controller
Ext.define('Fiddle.StoreBinderController', {
extend: 'Ext.app.ViewController',
alias: 'controller.storebinder',
storeUpdate: function (store) {
var recs = store.query('include', true);
this.getViewModel().set('includedTotal', recs.length)
}
});

Passing data to another view from controller and set a label's value in sencha touch

I have a controller, and I want to pass a simple string value to the next View.
For that, I am creating the View like this.
var nextView = Ext.create('MyApp.view.NextView', {
content: 'value'
});
Ext.Viewport.add(nextView);
Ext.Viewport.animateActiveItem(nextView, {
type: 'slide',
direction: 'left'
});
On the NextView, I have a label and I want to set the HTML property of the label to the value that I am passing from the controller. ie. value.
My NextView looks like this.
Ext.define('MyApp.view.NextView', {
extend: 'Ext.form.Panel',
config: {
content: 'null',
items: [{
xtype: 'label',
html: 'value'
}]
}
});
I am not sure how to proceed from here. I can't have the NextView as a form. I just need to pass one string value in this situation.
What's the best way to achieve this?
Use initialize method to access config data like this:
Ext.define('MyApp.view.NextView', {
extend: 'Ext.form.Panel',
config: {
content: 'null',
items: [
{
xtype: 'label',
html: 'value'
}
]
},
initialize : function(){
this.callParent();
var val = this.config.content;
this.down('label').setHtml(val);
}
});
PS Feel free to use your favourite selector in down function
I know the question has been answered. But I just digged up a pretty natural way to pass data from controller to a view (using view's constructor). I use this in my integration of web desktop to my app.
In controller, pass data to the constructor of the view as followed:
loiTab = Ext.create('Iip.view.giips.admission.DetailedLoiTab', {closable: true, selectedLoiData: selected[0].data});
In the view, spin up a constructor as followed:
constructor: function(selectedLoiData) {
Ext.applyIf(this, selectedLoiData);
this.callParent();
},
The following method lives in the same file as the constructor. You can access selectedLoiData from any where in the view the constructor lives as followed:
initComponent: function(){
console.log(this.selectedLoiData);
}

Object #<Object> has no method 'getStore' on the app init

I'm trying to get Store in the view on the init of the applicatoin, however the console tells me: Object #<Object> has no method 'getStore'
I'm wondering how would you get a store in this sequence:
Initialise app
Get user GPS location
Create a store
Display view with the store
init: function () {
this.callParent();
console.log("controller init");
},
launch: function () {
this.getApplicationSettings();
this.getApplicationRegionalisation();
Ext.getStore("appSettings").setValue("region", "Melbourne");
var store = Ext.create("APN.store.Locations");
var geo = Ext.create('Ext.util.Geolocation', {
autoUpdate: false,
listeners: {
locationupdate: function(geo) {
var location = store.getClosestLocation(geo.getLatitude(), geo.getLongitude());
Ext.getStore("appSettings").setValue("region", location.get("location"));
},
locationerror: function(geo, bTimeout, bPermissionDenied, bLocationUnavailable, message) {
}
}
});
And then in the view I would like to call something like this, correct me if I'm doing a stupid thing:
requires: [
'APN.store.AppSettings'
],
..... omitted stuff
// in items:
items: [
{
itemId: 'nav_home',
id: 'homeView',
group: '',
title: "Home",
layout: 'vbox',
slideButton: { selector: 'toolbar' },
settingsButton: { selector: 'toolbar' },
items: [{
xtype: 'articlelist',
id: 'latestNews',
category: 'Latest',
feedUrlName:
// here is the place where it bugs out!
Ext.getStore("appSettings").getValue(Ext.getStore("appSettings").getValue("region").get("menuItems").home.url,
}
],
},
Wherever you are creating the view, you can create the store before that and set store to the view explicitly so that when initialize function is executed it will get this.config.store. To get GPS location on app initialization, you can get location in Launch function of Ext.application before even creating the store and view. If you want to create view only after store data is loaded, you should create view in load callback of store.
I hope this is what you were looking for, if not please add some code to your question so that we can comment on specifics.

getRootNode() is not a function

I try to develop an app with MVC architecture. I've the following Controller code:
Ext.define('PM.controller.Projects', {
extend: 'Ext.app.Controller',
models: ['Project'],
stores: ['Projects'],
views: [
'projects.Tree',
'Toolbar',
],
init: function(config) {
var tree = this.getProjectsTreeView();
var rootNode = tree.getRootNode();
console.log(rootNode);
this.callParent(config);
}
});
And this view code:
Ext.define('PM.view.projects.Tree', {
extend: 'Ext.tree.Panel',
xtype: 'projectsTree',
title: 'Projects',
hideHeaders: true,
root: {
text: "Projekte"
}
});
It try to get the root node from my tree view in the controller but I get the error that getRootNode() is not a valid function in my controller. Can anybody tell me why I get this error? My target is to add new children to this root node from an ajax request.
Thanks
The methods Ext generates for each string in the views array return constructors that can be used to create the respective views. That seems bizarre, but that's how it is.
If you want to access the actual view component, you'll need to create a ref for it. Your init method should not assume that the view exists yet. It's very likely that it won't since the controller's init method is called before the application's launch method which is probably where all the views are getting added to the page.
You want to put your logic in the controller's onLaunch template method which is called after the application has been launched and your view has been added.
Ext.define('PM.controller.Projects', {
extend: 'Ext.app.Controller',
refs: [{
ref: 'projectsTreeView',
selector: 'projectsTree'
}],
init: function() {
// It's safe to add selectors for views that don't exist yet.
this.control(/*...*/)
},
onLaunch: function(config) {
var tree = this.getProjectsTreeView();
var rootNode = tree.getRootNode();
console.log(rootNode);
}
});
If this doesn't work, that means you aren't actually adding your view anywhere. One place you could add it is in the application's launch method. Something has to add the treeview.
Ext.application({
// ...
views: ['projects.Tree']
launch: function() {
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: [new this.getProjectsTreeView()]
});
}
});
So the chronology of events is this:
Application#constructor
Controller#constructor
Controller#init (can't assume the view exists)
Application#onBeforeLaunch
Application#launch (view is now added)
Controller#onLaunch (do something with the view that is now available)
Also, your view alias may need to be 'widget.projectsTree' not just 'projectsTree'.

In ExtJS components how to forward config: {} items to sub components

I am trying to write a reusable item selection panel where the user has a grid with items he can choose from and a small text field that he can use to filter the content of the grid. Right now the (simplified) view code looks like this and works.
Ext.define('MyApp.view.items.ItemSelectorPanel', {
extend: 'Ext.panel.Panel',
require: 'MyApp.view.items.SimpleItemGrid',
alias: 'widget.ItemSelectorPanel',
layout: 'form',
config: {
itemStore: false
},
constructor: function(config) {
this.initConfig(config);
this.superclass.constructor.call(this, config);
this.add([
{
fieldLabel: 'Filter',
name: 'filter'
},
{
xtype: 'SimpleItemGrid',
collapsible: true,
store: this.getItemStore()
}
]);
return this;
}
});
As you can see the ItemSelectorPanel uses the config property to expose an interface where the calling site can specify which item store to use.
Calling site (in this case the panel is added to a TabPanel):
var panelToAdd = {
xtype: 'panel',
title: 'New Selection',
closable: true,
padding: 10,
items: [{
title: 'Select node',
xtype: 'ItemSelectorPanel',
itemStore: itemStore
}]
};
Now, I love the declarative style of ExtJS 4 and how it helps to follow the MVC pattern. I would prefer to have the least amount of code possible in the views. Unfortunately this does not work:
Ext.define('MyApp.view.items.ItemSelectorPanel', {
/* ... same as above ... */
constructor: function(config) {
this.initConfig(config);
this.superclass.constructor.call(this, config);
return this;
},
items: [
{
fieldLabel: 'Filter',
name: 'filter'
},
{
xtype: 'SimpleItemGrid',
collapsible: true,
store: this.getItemStore // <-- interesting part
}
]
});
Is there a way to expose the config of a nested/sub component via the config property of the parent property in a declarative manner?
First something in general
Never add an object outside a function within a class definition unless you exactly know what you are going to do. Cause if you do so all instances will share the same instance of that object. I think I do not need to mention where this leads to...
If you have a need to place a object there you should clone it within the constructor.
To your code
I dunno what this.initConfig(config); does but the config variable is not the one from your class, it is the one from the constructor argument. I recommend you also to use initComponent() for initialization instead of the constructor() unless you have a defined need for using the constructor, which in your case you don't seem to have.
Also a 'config' is not forwarded cause it don't get executed up->bottom but bottom->up where a config get's hand up and all other properties are (already) inherited.
I still do not exactly know what your goal is, therefore I cannot give you any advice how you should do this but I can say for sure that the way you do it will lead to problems.
Edit
I still not sure that I have fully understand your needs but the following should work (if you need the listeners too you might take a look at the Ext.util.Bindable mixin)
Ext.define('MyApp.view.items.ItemSelectorPanel', {
extend: 'Ext.panel.Panel',
require: 'MyApp.view.items.SimpleItemGrid',
alias: 'widget.ItemSelectorPanel',
layout: 'form',
initComponent: function() {
// Initialize the store (might be a instance or a storeId)
var store;
if (this.itemStore) {
store = Ext.data.StoreManager.lookup(store);
}
this.itemStore = store || null;
// Add is not valid (at least not before the parent inits are executed)
this.items = [{
fieldLabel: 'Filter',
name: 'filter'
}, {
xtype: 'SimpleItemGrid',
collapsible: true,
store: this.getItemStore()
}];
this.callParent(arguments);
},
getItemStore: function() {
return this.itemStore;
}
});
No, you can't do it in the way you've described. The reason is pretty simple, let's take this as an example:
Ext.define('MyClass', {
someProp: this.foo(),
foo: function(){
return bar;
}
});
Here, we call the define() method and we pass it an object as the configuration. As such, the whole object (including the call to foo()) is evaluated before it's even passed to define, so the class/method doesn't even exist at that point.
Even if you could do that, here's also the complication that foo is an instance method on the class, but the way you're attempting to call it is as though it's a static method.
So, the answer is, you'll need to use some kind of method to do so, initComponent is typically preferred over the constructor.
You can define items in declaration of your class but you cannot call any method from your class at time of declaration. To solve it, define only items without store and than use initComponent method to set store for your view.
I didn't see an answer that addressed the original question. Here is what I've found to work ...
Creating an instance of myClass, passing in a config 'foo' with value 'bar'
var myClassInstance = Ext.create('MyApp.view.myClass', {
foo: 'bar'
});
myClass is defined as follows :
Ext.define('MyApp.view.myClass', {
extend: 'Ext.container.Container',
alias: 'widget.myclass',
config: {
foo: 'defaultVal'
},
constructor: function(configs) {
this.callParent(arguments); //create class, calls initComponent
var try1 = getFoo(); //try1 is 'defaultVal'
this.initConfig(configs); //initializes configs passed in constructor
var try2 = getFoo(); //try2 is 'bar'
},
initComponent: function() {
//myClass declaration here ...
this.callParent();
}

Resources