Getting form from click event in Controller in ExtJS? - extjs

I am extending the example given in http://docs.sencha.com/architect/2-0/#!/guide/views_forms_extjs to use a more MVC centric approach. So in service of that I want to move the on click handler from the view to a newly create controller.
I have the click even working fine, but I have no idea how to operate on the form from the context of the controller (the view was using this.getForm()).
Here is what I have so far,
Ext.define('LoginExample.controller.LoginController', {
extend: 'Ext.app.Controller',
onLoginButtonClick: function(button, e, options) {
console.log('button clicked');
if (this.getForm().isValid()) {
this.getForm().submit({
url: 'login.php',
success: function(form, action) {
Ext.Msg.alert('Login Successful!');
},
failure: function(form, action) {
Ext.Msg.alert('Login Failed!');
}
});
}
},
init: function() {
this.control({
"#loginButton": {
click: this.onLoginButtonClick
}
});
}
});
Obviously the this in the context of onLoginButtonClick is no longer the view and is instead the controller.
Given the parameters given to me, (Ext.button.Button button, Event e, Object options), how do I get the submit on the appropriate form?
I should note this using ExtJS 4.

button.up('form');
will do the trick.
To get the BasicForm object to operate on, use
button.up('form').getForm()

Related

Ext.form.Panel's beforesubmit listener does not respect return: false when attached via controller

I have a formpanel with a beforesubmit listener, which should prevent the submission if the form is invalid.
Sencha Fiddle availble here:
https://fiddle.sencha.com/#fiddle/3j5l (just comment the beforesubmit: 'onFormBeforeSubmit' line within the controler/panel and inspect the console to see the difference)
The listener is attached via a controller trough the init function like this:
//controller init function
init: function () {
var me = this;
me.listen({
component: {
'formpanel': {
beforesubmit: 'onFormBeforeSubmit'
}
}
});
},
onFormBeforeSubmit: function () {
console.log(arguments);
var me = this, form = me.getView();
console.log('beforesublit event fired');
if (!form.validate()) {
console.log('form is invalid!');
return false;
}
}
And all seems fine - the submit procedure is started, the onFormBeforeSubmit() method is executed, the form is considered invalid, but althought there is a return false statement - the form is submitted to the server.
Then, i tried to attach the listener simply via the listeners config of the panel like this:
//panel definitions...
listeners: {
beforesubmit: 'onFormBeforeSubmit'
}
And then it worked as expected.
As you can see the executed function is the same.
One thing i mentioned is that it receives different arguments - if triggered via the listeners config - it has a 5 arguments. Via controller - they are 4. The 5th one is an obect like this:
beforesubmit: "onFormBeforeSubmit"
scope: "self"
Can someone explain me why is this? Is it a bug or an expected behavior?
And after all - where is the right place to attach the listeners - in the controller or within the view??
Thanks.
First of all, you don't have to do this in init function.
Simply use control block of your viewcontroller like this:
control: {
formpanel: {
beforesubmit: 'onFormBeforeSubmit'
}
},
Please refer the documentation of control, it is much more straightforward to use that.
But it still not enough, and I think you are right, this is a bug. FormPanel's submit actually still using already deprecated function to fire events.
Please try the following override, it should fix this and allows you to use event listeners defined in controllers:
Ext.define('FixPanelEventFiring',{
override: 'Ext.form.Panel',
submit: function(options, e) {
var me = this,
formValues, form;
options = options || {};
formValues = me.getSubmitValues({
enabled: me.getStandardSubmit() || !options.submitDisabled
});
form = me.element.dom || {};
if (this.getEnableSubmissionForm()) {
form = this.createSubmissionForm(form, formValues);
}
options = Ext.apply({
url: me.getUrl() || form.action,
submit: false,
form: form,
method: me.getMethod() || form.method || 'post',
autoAbort: false,
params: null,
waitMsg: null,
headers: null,
success: null,
failure: null
}, options || {});
return me.fireEventedAction('submit',
[me, formValues, options, e],
'doBeforeSubmit',
this,
null,
'after'
);
},
});
Please be sure you include this in your overrides. You can also test this in fiddle, just add it before everything else. I'm not 100% sure it is perfect, I can imagine there are other issues with this, so please test this well.

Extjs6 Custom js event

I have the following flow: before the app launches I want to check something on the server. Based on the response I want to make a decision. I've created an utility class that wraps my js event and also an app controller.
Bellow is app controller:
Ext.define('MyApp.controller.AppController', {
extend: 'Ext.app.Controller',
appEventDispatcher:function (){
// Create a dummy DOM element
var dummy = document.createTextNode('');
// Create custom wrappers with nicer names
this.off = dummy.removeEventListener.bind(dummy);
this.on = dummy.addEventListener.bind(dummy);
this.trigger = function(eventName, data){
if( !eventName ) return;
var e = new CustomEvent(eventName, {"detail":data});
dummy.dispatchEvent(e);
}
}
});
And my utility class:
Ext.define('MyApp.util.Util', {
statics : {
checkSomethingOnServer: function(customEvent){
var store = Ext.StoreManager.lookup('appStore');
store.load({
scope: this,
callback: function(records, operation, success){
if (success === true){
customEvent.trigger('success', true);
if (success === false)
debugger;
customEvent.trigger('fail', true);
}
}
});
}
}
});
Using the utility class I load a store. In the callback method, I trigger my custom event. This event is handled in the app.js file.
The code works in fiddle and also using app watch, when I want to build the code some errors are occurring complaining(syntax error).
I've created also a fiddle.
How to create a custom event in ExtJS and how to use it? I need the same behavior as with the js event but Extjs implementation.
In ExtJS, you would just attach an event listeners to the store with your custom event's name:
store.on('myownevent', function(success) {
console.log(success);
});
and your code may go ahead and fire events on the store by that name:
store.load({
scope: this,
callback: function(records, operation, success){
store.fireEvent('myownevent', success);
}
});
If no listener for that event is attached, nothing happens; if one or more listeners are attached, they are executed in the order of priority, for those with the same priority, in the order they were added.
https://fiddle.sencha.com/#view/editor&fiddle/21g7

What would keep setter methods from being created in sencha touch

I have a simple controller that I'm starting to build in sencha touch 2:
Ext.define('ScoreKeeper.controller.GameScores', {
extend: 'Ext.app.Controller',
config: {
refs: {
requestButton: 'button[name=RequestButton]',
responseArea: '#scoreResponse'
},
control: {
requestButton: {
tap: 'sendRequest'
}
}
},
sendRequest: function (){
Ext.Ajax.request({
url: 'message',
method: 'GET',
disableCaching: false,
scope: this,
callback: function(options, success, response) {
this.setResponse(response.responseText);
}
});
},
setResponse: function (responseText){
this.setResponseArea(responseText);
}
});
When I tap my requestButton the sendRequest method fires correctly but when it gets to the setResponse method, it fails on the this.setResponseArea step.
When I checked my browser dev tools, it looks like the getter methods for both of my refs get created, but neither of the setter methods do.
What would keep these methods from being created?
Assuming #responseArea refers to an HTML element like a <div>. To attribute the response from your server to the HTML element, do this:
this.getResponseArea.setHtml(responseText);
instead of
this.setResponseArea(responseText);.
Don't forget getResponseArea gets you the HTML element with #responseArea as id. When you got it, you can do whatever you want with it; for example:
this.getResponseArea.setValue(responseText);
or
this.getResponseArea.getValue();.
That's why there is a get but no set.

extjs - how correctly call a controller method from another controller or closure

I'm new to extjs and I'm using the MVC architecture.
When my application references a method of a controller, I do it that way (in MyApp.Application):
Mb.app.getController('Main').myMethod();
It is already long, but I think this is the way to do.
When a controller calls it's own method in a closure, I was led to use this code (in MyApp.controller.Main:
controllerMethodOne: function(){
Ext.Ajax.request({
url: ...,
params: ...,
success: (function(response){
list = Ext.JSON.decode(response.responseText);
list.forEach(function(item){
storeMenu.add(
Ext.create('Ext.menu.Item', {
text: item.text,
handler: function(el){MyApp.app.getController('Main').controllerMethodTwo()}
})
)
})
})
})
},
I referenced the method with MyApp.app.getController('Main').controllerMethodTwo() because this is not refering to the controller object in the closure, and thus this..controllerMethodTwo()isn't working.
I find this utterly convoluted, and I hope someone has an idea to get around that MyApp.app.getController-workaround.
Update
Thanks to all the suggestion I could optimize my code and came up with:
// in my controller
mixins: ['Mb.controller.mixin.StoreMenu'],
// I use that style of menus in two controllers thats why I use a mixin
init: function() {
this.control({
'#vg_storeMenu menuitem': {
click: this.onStoreMenuClicked
}
})
},
// the controller mixin
Ext.define('Mb.controller.mixin.StoreMenu', {
extend: 'Ext.app.Controller',
buildStoreMenu: function(store_name){
var storeMenu = Ext.ComponentQuery.query('#' + store_name + 'Menu')[0];
Ext.Ajax.request({
url: Paths.ajax + 'json.php',
params: {list: store_name + 's'},
success: (function(response){
list = Ext.JSON.decode(response.responseText);
items = Ext.Array.map(list, function(item) {
return {
xtype: 'menuitem',
text: item.text
}
});
storeMenu.add(items);
})
})
},
onStoreMenuClicked: function(el){
...
}
});
Actually, there are at least four distinctly different problems in your code:
Scope handling for intra-class method calls
Component creation inefficiency
Component event handling in a controller
Inter-controller communication
Scope handling
The first one is solved either by using a closure, or passing in the scope parameter to Ajax request, as #kevhender described above. Given that, I'd advocate writing clearer code:
controllerMethodOne: function() {
Ext.Ajax.request({
url: ...,
params: ...,
scope: this,
success: this.onMethodOneSuccess,
failure: this.onMethodOneFailure
});
},
// `this` scope is the controller here
onMethodOneSuccess: function(response) {
...
},
// Same scope here, the controller itself
onMethodOneFailure: function(response) {
...
}
Component creation
The way you create menu items is less than efficient, because every menu item will be created and rendered to the DOM one by one. This is hardly necessary, either: you have the list of items upfront and you're in control, so let's keep the code nice and declarative, as well as create all the menu items in one go:
// I'd advocate being a bit defensive here and not trust the input
// Also, I don't see the `list` var declaration in your code,
// do you really want to make it a global?
var list, items;
list = Ext.JSON.decode(response.responseText);
items = Ext.Array.map(list, function(item) {
return {
xtype: 'menuitem',
text: item.text
}
});
// Another global? Take a look at the refs section in Controllers doc
storeMenu.add(items);
What changes here is that we're iterating over the list and creating a new array of the soon-to-be menu item declarations. Then we add them all in one go, saving a lot of resources on re-rendering and re-laying out your storeMenu.
Component even handling
It is completely unnecessary, as well as inefficient, to set a handler function on every menu item, when all this function does is call the controller. When a menu item is clicked, it fires a click event - all you need to do is to wire up your controller to listen to these events:
// Suppose that your storeMenu was created like this
storeMenu = new Ext.menu.Menu({
itemId: 'storeMenu',
...
});
// Controller's init() method will provide the wiring
Ext.define('MyController', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
// This ComponentQuery selector will match menu items
// that descend (belong) to a component with itemId 'storeMenu'
'#storeMenu menuitem': {
click: this.controllerMethodTwo
}
});
},
// The scope is automatically set to the controller itself
controllerMethodTwo: function(item) {
...
}
});
One best practice is to write the ComponentQuery selectors as finely grained as feasible, because they're global and if you're not precise enough your controller method may catch events from unwanted components.
Inter-controller communication
This is probably a bit far fetched at the moment, but since you're using Ext JS 4.2 you may as well take advantage of the improvements we've added in that regard. Before 4.2, there was a preferred (and only) approach to call one controller's methods from another controller:
Ext.define('My.controller.Foo', {
extend: 'Ext.app.Controller',
methodFoo: function() {
// Need to call controller Bar here, what do we do?
this.getController('Bar').methodBar();
}
});
Ext.define('My.controller.Bar', {
extend: 'Ext.app.Controller',
methodBar: function() {
// This method is called directly by Foo
}
});
In Ext JS 4.2, we've added the concept of event domains. What it means is that now controllers can listen not only to component's events but to other entities events, too. Including their own controller domain:
Ext.define('My.controller.Foo', {
extend: 'Ext.app.Controller',
methodFoo: function() {
// Effectively the same thing as above,
// but no direct method calling now
this.fireEvent('controllerBarMethodBar');
}
});
Ext.define('My.controller.Bar', {
extend: 'Ext.app.Controller',
// Need some wiring
init: function() {
this.listen({
controller: {
'*': {
controllerBarMethodBar: this.methodBar
}
}
});
},
methodBar: function() {
// This method is called *indirectly*
}
});
This may look like a more convoluted way to do things, but in fact it's a lot simpler to use in large(ish) apps, and it solves the main problem we've had: there is no need for hard binding between controllers anymore, and you can test each and every controller in isolation from others.
See more in my blog post: Controller events in Ext JS 4.2
this doesn't work in the success callback because it doesn't have the right scope. Your 2 options are to:
1: Create a variable at the beginning of the function to reference in the callback:
controllerMethodOne: function(){
var me = this;
Ext.Ajax.request({
url: ...,
params: ...,
success: (function(response){
list = Ext.JSON.decode(response.responseText);
list.forEach(function(item){
storeMenu.add(
Ext.create('Ext.menu.Item', {
text: item.text,
handler: function(el){me.controllerMethodTwo()}
})
)
})
})
})
},
2: Use the scope config of the Ext.Ajax.request call:
controllerMethodOne: function(){
Ext.Ajax.request({
url: ...,
params: ...,
scope: this,
success: (function(response){
list = Ext.JSON.decode(response.responseText);
list.forEach(function(item){
storeMenu.add(
Ext.create('Ext.menu.Item', {
text: item.text,
handler: function(el){me.controllerMethodTwo()}
})
)
})
})
})
},

select a one of the containers from panel in extjs

Extjs 4.1.1(a), In my project, there is a panel (with Id #monthCalendar) which has 42 containers inside it in a View. I am trying to create a controller for that view. Here the controllers action is to show "hello" message whenever I click on any of the container inside the panel. I tried the following which is not showing any kind of error in chrome console.
In my controller:
onLaunch: function(){
Ext.each(Ext.ComponentQuery.query('#monthCalendar container'),function(container){
container.on('click',function(){
alert("hello");
},container,{element: 'el'})
})
}
This one should work
Ext.each(Ext.ComponentQuery.query('#monthCalendar container'),function(c){
c.on({ click: {fn: function(){ alert("hello"); },scope: this, element:'el' }})
})
It seems the containers inside the panel were not redered when the click event was called.(though, the containers were visible on the page. I don't know what possibly the bug is?) So, instead of using onLaunch, I used init template in which I called the render event (indirectly called the click event) and this worked.
init: function(){
this.control({
'#monthCalendar container': {
render: this.onContainerRendered
}
})
},
onContainerClicked: function() {
alert('The container was clicked');
},
onContainerRendered: function(container) {
container.on('click',this.onContainerClicked,container,{element: 'el'})
},
Working Fiddle

Resources