Sencha Touch - Why Is This Function Undefined in my Custom Component? - extjs

I'm trying to navigate the Sencha class system, and seem to be failing in this aspect.
I have a Carousel that I'm adding Components too. I have a Store with records, and I'm looping through the records and adding a custom Component to the Carousel in each iteration. Here's the code for that...
var carousel = Ext.create("Ext.Carousel", {
fullscreen: true
});
sights.each(function(sight, index, length){
carousel.add(Ext.create("Parks.view.ImageView", {
imageName: sight.get("img"),
titleName: sight.get("name")
}));
});
My custom Component has the following code, but is failing to execute because of the getImageName() function. It's complaining it's not defined. But, based on my understanding of the Sencha class structure, it should have been defined by the constructor on initialization?
Ext.define("Parks.view.ImageView", {
extend: "Ext.Panel",
fullscreen: true,
config: {
imageName: "",
titleName: ""
},
constructor: function(config){
this.initConfig(config);
},
items: [
{
xtype: "img",
layout: "fit",
src: getImageName()
}
]
});

There's a mistake hiding another mistake in your code.
First, it should be this.getImageName(). But even then it will not work because you need this to point to an instance of your class to call this method (maybe you should read about scope in Javascript a bit... That's quite a spicy subject!).
Here, you must realize that your function will be called before the constructor, and even before Ext.define for that matter (because the return value of your method is needed for the src property of the object that is in the items property of the object that you pass as argument to Ext.define).
When you need to do some processing (i.e. execute a function) to create the configuration of a component, override its initialize method, like this:
Ext.define("Parks.view.ImageView", {
extend: "Ext.Panel",
fullscreen: true,
config: {
imageName: "",
titleName: "",
layout: "fit"
},
// This is not needed, and it will break everything. You're extending from
// a class that already calls initConfig for you. And you don't call the parent
// method, so you're completely removing the component initialization cycle.
//
// constructor: function(config){
// this.initConfig(config);
// },
initialize: function() {
// you should test that you have a value in imageName before using it
this.add({
xtype: "img",
layout: "fit",
src: this.getImageName()
});
// this one is probably not needed because the parent method is empty (it is
// is what Ext calls a template method), *but* it is more safe to call it
// anyway -- in case some override put some code in this method upper in
// the class hierarchy
this.callParent(arguments);
}
});
Edited: my answer applied to ExtJS, it didn't work with Touch...

Related

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

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

ExtJS Class constructor

I have a custom class created
Ext.define('MyFormPanel, {
extend: 'Ext.form.Panel',
field1: null,
field2: null
constructor: function (config) {
this.createFields();
config.items.splice(0, 0, [
this.field1,
this.field2
]
this.callParent([config]);
}
});
However it will not add my fields to the form. However, if I swap at the config.items.splice for
config.items[0] = this.field1;
config.items[1] = this.field2;
The form panel is created correctly.
My question is am I using the splice command incorrectly? Is there an alternative?
You are not using splice correctly. It takes the elements to add as separate arguments, not an array
There are other problems:
Name of class in missing a quote to close it
Your call to splice is not closed, missing a )
You have to make sure items is an array before you can call splice on it
Config options have not been copied to this yet.
Try the following https://fiddle.sencha.com/#fiddle/8ij
Ext.define('MyFormPanel', {
extend: 'Ext.form.Panel',
field1: null,
field2: null
constructor: function (config) {
this.createFields();
config.items = config.items || [];
config.items.splice(0, 0, config.field1, config.field2);
this.callParent([config]);
}
});
You should not override constructor. You should override initComponent
Ext.define('MyFormPanel', {
extend: 'Ext.form.Panel',
field1: null,
field2: null
initComponent: function () {
this.createFields();
this.items = this.items || [];
this.items.splice(0, 0, this.field1, this.field2);
this.callParent();
}
});
From a readability perspective it makes it very hard to determine what your constructor is trying to do. Some improvements:
Move all the legwork to create fields into the initComponent as Juan recommends. This includes any dependencies on the fields in the current constructor to the parent initComponent.
You need to start invoking the super method with the special JavaScript reserved word "arguments" (as opposed to [config]) this will call another method with the argument signature which invoked the current method.
Pretty sure the only time you should need to override constructor instead of initComponent is when you are working from custom classes which don't extend off any component.
Why aren't you able to use the Panel#add() method to add your fields in initComponent? Splice feels hacky given the Ext API's allow you to add fields directly.
Ext.define('MyFormPanel, {extend: 'Ext.form.Panel',
initComponent: function () {
this.callParent(arguments);
this.createFields();
},
createFields : function() {
this.field1 = Ext.create('YourField1', {});
this.add(this.field1);
this.field2 = Ext.create('YourField2', {});
this.add(this.field2);
}});

Sencha Touch access method in parent view

I'm trying to access a function from a component initialize. New to Sencha Touch/Ext.js, so not sure how scope works. I have 'spin' listeners in the 'control' which seem to work well, when incremented.
Simplified Code:
Ext.define('App.view.CreateChallenge', {
extend: 'Ext.Container',
fullscreen: true,
xtype: 'CreateChallenge',
config:{
items[
xtype: 'spinnerfield',
itemId: 'mySpin',
initialize:function(){
App.view.Challenge.doThis();
}
],
control:{
'#mySpin' : {
spin: 'doThis'
}
}
},
doThis:function(){
console.log('dooing this');
}
});
Thanks,
Steve
First of all, you should not override initialize method in the config block, the proper place for that is in Ext.define.
Then, in initialize, you can call App.view.CreateChallenge.doThis(). But it just an ordinary function, its scope is the global window object, so you don't have access to the object of App.view.CreateChallenge.
To find a container reference from its children, you can use Ext.Component.up(). In your case, it will look like:
initialize: function() {
…
this.up('CreateChallenge').doThis(); // find container by xtype then call its method
}
We are not there yet! If you try, you are going to get an undefined from this.up(…). This is because the container isn't finished constructing while its children are initializing. So, if you need a reference of the container in the initialize method, you need to wait until the container is ready, such as the painted event.
Then, you will end up something looks like:
initialize: function() {
…
this.on('painted', function () {
this.up('CreateChallenge').doThis(); // find container by xtype then call its method
}, this);
}
I would simply use
VIEW/This .getParent()

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

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