I have a field that is stateful, and I also have it hooked up to the change event... when its value changes, I want to perform some operation. However, because it's a stateful field, the change event fires when I go back to this view, and unfortunately, the change event fires before the ViewController's init method, which means I will not be able to access my reference lookup.
In the following example, run it, change the date, and then re-run the application... you'll see a console.log that appears for the change, and then for the init. I realize I could set up the handler in the init method, but that just seems silly. I also realize I could create myField as a private var and access it that way, but that also seems silly. And yes, I could change to the select event, but that's not what I want to do. Anyone have any thoughts? Here's an example:
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
Ext.define('MyViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.myView',
init: function() {
console.log('init fired', this.lookupReference('myField'))
},
onChange: function(value) {
console.log('onChange fired', this.lookupReference('myField'));
}
});
Ext.define('MyView', {
extend: 'Ext.container.Container',
controller: 'myView',
renderTo: Ext.getBody(),
items: [{
xtype: 'datefield',
value: new Date(),
stateful: true,
stateId: 'blahblah',
listeners:{
change: 'onChange'
}
}, {
xtype: 'datefield',
value: new Date(),
reference: 'myField'
}]
});
Ext.create('MyView');
}
});
This is because the state mixin is initialized before the controller, this is code taken directly from Ext.Component's constructor:
me.mixins.state.constructor.call(me);
me.addStateEvents('resize');
controller = me.getController();
if (controller) {
controller.init(me);
}
There is no config to change this behavior. Honestly, I've never seen someone make a form field's value stateful.
You can use the buffer config to delay event firing.
This has an advantage of setting up the event after the controller is initialised.
The solution:
listeners: {
change: {
buffer: 300,
fn: 'onChange'
}
}
An Alternative is to handle 'beforestaterestore` event of the stateful field and apply the state value only after controller is initialised.
listeners: {
beforestaterestore: function (field, state){
var controller = field.up().getController();
Ext.Function.interceptAfter(controller, 'init', function(){
field.setValue(state.value); // update
},this);
return false;
}
}
Related
I'm having a real hard time understanding how to access the actual bound store instance on a component, like a ComboBox or Grid. It appears that when you use their getStore method, it returns a different instance.
In my example, I have a custom grid class, and in its initComponent, I want to add a load listener. Unfortunately, the load listener never gets hit, but the bound store's load listener does. You can see the listener is getting set up before the store is loaded, so that's not the issue. It looks like the store instance in initComponent is a memory store, so the bound store's instance is not synced to the component at this point.
I know I can have a load listener on the bound store, but this is a custom grid class, and for any custom grid class, I want there to be a listener set up... so I don't want to have to do this all over the place in my code... I just want it in one class, as they're all going to have the same logic carried out.
Can someone explain to me why my custom grid class's load listener is not getting hit, and what can I do to fix this? Can I somehow access the bound store instance?
Ext.application({
name: 'Fiddle',
launch: function() {
Ext.define('MyGrid', {
extend: 'Ext.grid.Panel',
xtype: 'myGrid',
initComponent: function() {
alert('initComponent')
this.callParent();
this.getStore().on('load', this.onLoad, this);
},
onLoad: function() {
alert('grid store loaded');
}
});
Ext.define('MyViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.myview',
onLoadBoundStore: function() {
alert('loaded bound');
}
})
Ext.define('MyViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.myview',
stores: {
myStore: {
fields: ['name', 'value'],
autoLoad: true,
proxy: {
type: 'ajax',
url: 'data1.json',
reader: {
type: 'json'
}
},
listeners: {
load: 'onLoadBoundStore'
}
}
}
});
Ext.define('MyView', {
extend: 'Ext.form.Panel',
renderTo: Ext.getBody(),
controller: 'myview',
viewModel: {
type: 'myview'
},
items: [{
title: 'blah',
xtype: 'myGrid',
reference: 'myComboBox',
bind: {
store: '{myStore}'
},
columns: [{
text: 'Name',
dataIndex: 'name'
}, {
text: 'Value',
dataIndex: 'value'
}]
}]
});
Ext.create('MyView')
}
});
Can someone explain to me why my custom grid class's load listener is
not getting hit, and what can I do to fix this?
You are not initially providing any store to your grid (the store config option is required by the way). Hence the grid is creating its own, empty store as coded here:
store = me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
That empty store is the one that gets your onLoad listener attached. It is not getting hit because the store is never loaded.
Can I somehow access
the bound store instance?
Yes, use this:
this.lookupViewModel().getStore('myStore')
You can attach your listener to the bound store instead of the empty one and see the difference. The grid's empty store eventually gets replaced by the bound one (this is why you see data in the grid, after all), but this happens after initComponent is executed. You can track the moment of setting the bound store to the grid by overriding setStore method.
I know I can have a load listener on the bound store, but this is a
custom grid class, and for any custom grid class, I want there to be a
listener set up... so I don't want to have to do this all over the
place in my code... I just want it in one class, as they're all going
to have the same logic carried out.
Since you are using MVVC it is recommended to stick to the pattern and keep views declaring view aspects only. Listeners should be declared and handled in controllers (not even in viewmodels like in your example). Otherwise, if you continue sticking to pre Ext JS 5 style and put dynamic stuff in views (i.e. the grid in your case) there is no point in using MVVC.
This is so incredibly hacky that I don't even want to post it as an answer, but I'm going to just for posterity sake. Building on Drake's answer, I figured out how to get my store using what was passed in the initial config of my grid class. The bind comes in with the braces, so that's why I'm doing a replace on it. Like I said, this probably should never be used, but it's something.
initComponent: function() {
var store = this.lookupViewModel().getStore(this.config.bind.store.replace(/[{}]/g, ''));
store.on('load', this.onLoad, this);
this.callParent();
},
Just to add one more option to the mix, you can listen to the reconfigure event on the grid, as that's what fires after binding a store is complete... so something like this would work:
Ext.application({
name: 'Fiddle',
launch: function() {
Ext.define('MyGrid', {
extend: 'Ext.grid.Panel',
xtype: 'myGrid',
initComponent: function() {
alert('initComponent')
this.on('reconfigure', this.onReconfigureGrid, this);
this.callParent();
},
onReconfigureGrid: function(grid, store, columns, oldStore, oldColumns, eOpts) {
store.on('load', this.onLoad, this);
},
onLoad: function() {
alert('grid store loaded');
}
});
Ext.define('MyViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.myview',
onLoadBoundStore: function() {
alert('loaded bound');
}
})
Ext.define('MyViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.myview',
stores: {
myStore: {
fields: ['name', 'value'],
autoLoad: true,
proxy: {
type: 'ajax',
url: 'data1.json',
reader: {
type: 'json'
}
},
listeners: {
load: 'onLoadBoundStore'
}
}
}
});
Ext.define('MyView', {
extend: 'Ext.form.Panel',
renderTo: Ext.getBody(),
controller: 'myview',
viewModel: {
type: 'myview'
},
items: [{
title: 'blah',
xtype: 'myGrid',
reference: 'myComboBox',
bind: {
store: '{myStore}'
},
columns: [{
text: 'Name',
dataIndex: 'name'
}, {
text: 'Value',
dataIndex: 'value'
}]
}]
});
Ext.create('MyView');
}
});
I'm in the need to listen to a enable/disable event on a container and I've noticed that there's no such event. I tried to listen to it in case it wasn't mentioned in the docs but it definitely doesn't exist.
I google'd around a little and found this..
Ext.define('My.Custom.Container',{
extend:'Ext.Container',
onEnable:function(){
console.log("it's listening");
}
});
It looked promising but it didn't work at all.
Is there any way to listen to these events? I don't want to get jQuery mixed in here as it would be an overkill.
The container xtype from which many components inherit methods does have an event which fires on changing of it's disabled state. This event is called disabledchange and fires every time the container is either enabled/disabled.
In the below code we can see this in action working on a button purely as it gives a visually better demo (since you can immediately see the state) but it inherits the exact same method and event from Container.
Ext.application({
name: 'Fiddle',
launch: function() {
Ext.Viewport.add({
xtype: 'container',
padding: 10,
items: [{
xtype: 'button',
text: 'Button To be disabled/enabled',
listeners: {
disabledchange: function(button, value, oldValue, eOpts) {
console.log('changing my disabled state to ' + value);
}
}
}, {
xtype: 'button',
text: 'click to disable/enable above button',
listeners: {
tap: function() {
var isButtonDisabled = Ext.Viewport.query('button')[0].getDisabled();
Ext.Viewport.query('button')[0].setDisabled(!isButtonDisabled);
}
}
}]
});
}
});
Demo: https://fiddle.sencha.com/#fiddle/g46
Ok, I just found an answer to my question.
Ext.define('My.custom.Container',{
extend:'Ext.Container',
config:{
....config....
},
enable: function(){
this.callParent(arguments);
// do something else;
},
disable: function(){
this.callParent(arguments);
// do something else;
}
});
I'm still curious if there's another way around this though. So answers are still welcome.
I'm trying to get a custom extjs component to render either a green-check or red-x image, based on a true/false value being bound to it.
There's a couple of other controls that previous developers have written for rendering custom labels/custom buttons that I'm trying to base my control off but I'm not having much luck.
I'd like to be able to use it in a view as follows where "recordIsValid" is the name of the property in my model. (If I remove the xtype: it just renders as true/false)
{
"xtype": "booldisplayfield",
"name": "recordIsValid"
}
Here's what I have so far, but ExtJS is pretty foreign to me.
Ext.define('MyApp.view.ux.form.BoolDisplayField', {
extend: 'Ext.Component',
alias : 'widget.booldisplayfield',
renderTpl : '<img src="{value}" />',
autoEl: 'img',
config: {
value: ''
},
initComponent: function () {
var me = this;
me.callParent(arguments);
this.renderData = {
value: this.getValue()
};
},
getValue: function () {
return this.value;
},
setValue: function (v) {
if(v){
this.value = "/Images/booltrue.png";
}else{
this.value = "/Images/boolfalse.png";
}
return this;
}
});
I'd taken most of the above from a previous custom linkbutton implementation. I was assuming that setValue would be called when the model-value for recordIsValid is bound to the control. Then based on whether that was true or false, it would override setting the value property of the control with the correct image.
And then in the initComponent, it would set the renderData value by calling getValue and that this would be injected into the renderTpl string.
Any help would be greatly appreciated.
You should use the tpl option instead of the renderTpl one. The later is intended for rendering the component structure, rather that its content. This way, you'll be able to use the update method to update the component.
You also need to call initConfig in your component's constructor for the initial state to be applied.
Finally, I advice to use applyValue instead of setValue for semantical reasons, and to keep the boolean value for getValue/setValue.
Ext.define('MyApp.view.ux.form.BoolDisplayField', {
extend: 'Ext.Component',
alias : 'widget.booldisplayfield',
tpl: '<img src="{src}" />',
config: {
// I think you should keep the true value in there
// (in order for setValue/getValue to yield the expected
// result)
value: false
},
constructor: function(config) {
// will trigger applyValue
this.initConfig(config);
this.callParent(arguments);
},
// You can do this in setValue, but since you're using
// a config option (for value), it is semantically more
// appropriate to use applyValue. setValue & getValue
// will be generated anyway.
applyValue: function(v) {
if (v) {
this.update({
src: "/Images/booltrue.png"
});
}else{
this.update({
src: "/Images/boolfalse.png"
});
}
return v;
}
});
With that, you can set your value either at creation time, or later, using setValue.
// Initial value
var c = Ext.create('MyApp.view.ux.form.BoolDisplayField', {
renderTo: Ext.getBody()
,value: false
});
// ... that you can change later
c.setValue(true);
However, you won't be able to drop this component as it is in an Ext form and have it acting as a full fledged field. That is, its value won't be set, retrieved, etc. For that, you'll have to use the Ext.form.field.Field mixin. See this other question for an extended discussion on the subject.
I have a controller with a store, a model, and some views.
I need to listen for the beforesync and write event of the store in the controller, but I don't know how to set these listeners in the controllers control-function.
My store looks like this :
Ext.define('DT.store.UsersStore', {
extend : 'Ext.data.Store',
model : 'DT.model.User',
id : 'myStore'
autoSync : true,
proxy : {
type : 'ajax',
api : {
read : '/load_entries',
update : '/update_entry'
},
reader : {
type : 'json',
root : 'user',
successProperty : 'success'
}
}
});
Now I try to listen to the events in my controller :
...
init : function () {
this.control({
'myStore' : {
beforesync : this.doSomething,
write : this.doSomethingElse
}
});
},
...
My expected result is that the functions will be executed, when the events are fired.
But at this time nothing happens when they are fired.
How can I get this to work?
Your way is possible but it's not ideal, IMO. The better way is to use controllers's store getter. In your case the code would be something like this:
init : function () {
// every controller has getters for its stores.
// For store UsersStore getter would be getUsersStoreStore()
this.getUsersStoreStore().addListener('write',this.finishedLoading, this);
this.control({
// widgets event handlers
});
},
Here is an alternative syntax to Molecular Man's answer.
Instead of writing,
init : function () {
this.getUsersStoreStore().addListener('write',this.finishedLoading, this);
this.control({
// widgets event handlers
});
},
You can write
init : function () {
this.getUsersStoreStore().on({
write: this.finishedLoading,
scope: this
});
this.control({
// widgets event handlers
});
},
I think this alternative definition reads a little bit better.
I took this from an answer Izhaki gave me.
As for Extjs 4.2.1, your initial way of accessing the store listener would actually work, if you were using the 'storeId' instead of the id and the 'listen' function instead of 'control':
Ext.define('DT.store.UsersStore', {
extend : 'Ext.data.Store',
model : 'DT.model.User',
storeId : 'myStore'
....
init : function () {
this.listen({
store: {
'#myStore' : {
beforesync : this.doSomething,
...
Ext.define('Store', {
model: 'Model',
extend: 'Ext.data.Store',
listeners: {
'beforesync': function(){
App.getController('somecontroller').onBeforeSync();
}
}
});
App - your application object
The function onBeforeSync you can implement it in the controller ... this is the only way i could assign the event to the store and still implement the logic in the controll. I hope it helps
I solved it by myself.
I added the listener manually in the render-event of my Panel
Ext.getCmp('userPanel').down('gridpanel').getStore().addListener('write',this.finishedLoading, this);
Thank you for the help #nscrob.
Hope this addition will help someone out there to avoid spending some hours on debugging the library core:
Related to this practice of accessing Ext.data.Store instance inside Controller using the controller's getter method: e.g. for the "DT.store.UsersStore" above using this.getUsersStoreStore():
pay attention that if the store is already associated in a view(e.g. was declared as the store property for a "UsersGrid" Grid.panel.Panel widget definition) then this getter method will retrieve in fact another instance of the same Store class and not the instance used by the widget!
The reason is that adding the store in the constructor configuration object like this:
stores: ['UsersStore']
will in fact add a new store instance in Ext.data.StoreManager.map hash so - supposing that 'UsersStore' is the only Store object instantiated so far - the map keys now look like:
0: "ext-empty-store"
1: "UsersStore"
2: "myStore"
Now imagine you want to read some new data using your store'proxy and display this new data in the "UsersGrid" and you want to do all these when user clicks on something, so inside controller you will have a handler method for the user event with the code:
'user-selector' : {
click: function(){
var oStoreReference = this.getUsersStoreStore();
oStoreReference.load( {params:{} });
}
}
That call to get the reference will be translated internally in this.getStore('UsersStore') and will return a reference to the controller generated - 1: "UsersStore" - and not - 2: "myStore" - as one might expected. Furthermore, the load() call will load the UsersStore instance with the new Models and this will not be reflected in your grid view(because the grid is bound and listens to the events generated by "myStore" store instance).
So better access the store by its itemId using the general getStore method: this.getStore('storeItemId')
Why not just relay the store's events? For example:
this.getUsersGrid().relayEvents(this.getUsersStoreStore(), ['write'], 'store')
And then be able to
this.control('somegrid-selector': {storeWrite: function(){...}})
Just in case somebody stumbels over this.
ExtJS 7.3
Ext.define('DT.controller.UsersStore', {
extend: 'Ext.app.Controller',
listen: {
store: {
UsersStore: {
beforesync : 'doSomething',
write : 'doSomethingElse'
}
}
},
doSomething() {
console.log(arguments)
},
doSomethingElse() {
console.log(arguments)
}
});
someClass = Ext.extend(someClassB, {
_someFunctionC{
someButton = new Ext.button({
handler: function () {
this._onClick('click');
}
}),
_onClick(someMessage){
Ext.Msg.alert(someMessage);
}
}
}
_onClick eats one parameter; in the above code you put in the 'click' event because you want _onClick to be executed after the user clicks on the button. However, how do you specify this specific 'click' registration AND pass in a local variable as the _onClick parameter at the same time?
As an aside, why do you even have to specify 'click', when the API states that handler always pertains to a click? Is this additional information not unnecessary?
Typically you set it up like this. No real need to pass parameters since someFunction is a member of your 'class' and has access to any data you'd want.
var button = new Ext.Button({
handler: this.someFunction
scope: this
});
someFunction: function() {
// do something interesting.
}
So if i understand correctly you want to set the handler config option but set the arguments yourself in one go?
Does this do what you want?
// clicking the button alerts 'Hello World'
new Ext.Button({
text: 'Test',
handler: function(value){
alert('Hello, ' + value);
}.createCallback('World')
});
Notice the createCallback executed on the anonymous function, this creates a callback function for handler which only gets passed the arguments you pass to createCallback.
Another way that I've found to do this is to pass a custom config option along with your button. Say you wanted to have a splitbutton that could choose the amount of banners to add. (this is from a recent project)
{
xtype: 'splitbutton',
iconCls: 'icon addBanners',
ref: '../addBanner',
text: 'Add Banner',
menu: new Ext.menu.Menu({
items: [{
text: 'Add 10 Banners',
scope: this,
handler: this.addBanner,
numBanners: 10
},{
text: 'Add 20 Banners',
scope: this,
handler: this.addBanner,
numBanners: 20
}]
}),
scope: this,
handler: this.addBanner,
numBanners: 1
}
And in your function:
addBanner: function(button, event) {
if (button.numBanners) {
// do whatever
}
}
You can also create a callback function that inserts extra parameters when it is called:
var button = new Ext.Button({
handler: this.someFunction.createDelegate(button,['Some message'])
});