Add change event handler for custom Container of ExtJS - events

I have created a View by extending Ext.container.Container and have given it an alias widget.myCustomView. Since I'm using it in different places.
The view as usual Ext form components like textfield, dataview, etc. Now I adding this view into other view using xtype as follows:
{
xtype: 'myCustomView',
itemId: 'myCustomView'
}
Now, I want to add change event handler such that if any component's change is fired, I can fire the change event of myCustom view. In short, do something like this.
{
xtype: 'myCustomView',
itemId: 'myCustomView',
listeners: {
'change' : function(viewObj, eOpts) {
//do something
}
}
}
How to do it?

Use the relayEvents() method to... well, relay the change event from child fields.
Here's some basic code that does that:
Ext.define('My.Container', {
extend: 'Ext.Container'
,layout: 'form'
,initComponent: function() {
this.callParent(arguments);
// i want to support nested containers
this.parseContainerItems(this);
}
,onItemAdded: function(item) {
if (item instanceof Ext.Container) {
this.parseContainerItems(item);
} else if (item instanceof Ext.form.field.Base) {
this.relayEvents(item, ['change']);
}
}
,parseContainerItems: function(ct) {
if (ct.items) {
ct.items.each(this.onItemAdded, this);
}
}
});
Example usage:
Ext.create('My.Container', {
renderTo: 'ct' // render to a test div
,height: 200
,width: 200
,items: [{
xtype: 'textfield', name: 'foo', fieldLabel: 'Foo'
},{
xtype: 'container'
,items: [{
xtype: 'checkbox', name: 'bar', fieldLabel: 'Bar'
}]
}]
,listeners: {
change: function(item, newValue, oldValue) {
console.log(Ext.String.format('Value of item {0} changed from {1} to {2}', item.name, oldValue, newValue));
}
}
});
Going further...
As I've said my implementation is quite rudimentary since it only supports fields that are added to the container by configuration. If you want to make that component flexible, you'll have to handle fields that are added after the component creation.
For that you'll need to watch the add event of the container to relay from fields that are added after its creation. The doc says that this event bubbles from child containers, but from my tests it does not :-( So (until that's fixed?) you'll also have to watch the add event of child containers.
Here's the updated code for the parseContainerItems() method:
parseContainerItems: function(ct) {
ct.on('add', function(me, item) {
this.onItemAdded(item);
}, this);
if (ct.items) {
ct.items.each(this.onItemAdded, this);
}
}
If you also want to support the possibility of removing fields dynamically, that's when things will go awry... You'd have to implement your own version of relayEvents() because, as far as I know, it is not possible to stop relaying events with the one provided by Ext. Then you'd have to watch the remove event to remove the listeners you've added to child fields and containers.

Related

ExtJS: How to let user enter a value in Combobox that is not in store

I have a combobox which is a list of various addresses. When any address is selected, the select event plots this address on map. I want to change this program in such way that, if a user enters an address which is not a part of the Combo store, this address should still be plotted on the map. I do not require to modify the combo store. How can this be done? What event listener can trigger such event? Do I need to swap to a textfield?
{
xtype: 'combobox',
name: 'address1',
style: {
marginLeft: '15px'
},
store: Ext.create('MyStore'),
valueField: 'address',
displayField: 'address',
triggerAction: 'all',
emptyText: 'Select Address',
typeAhead: true,
minChars: 2,
typeAheadDelay: 100,
queryMode: 'remote',
width: 300,
queryParam: 'searchAddress',
hideTrigger:true,
listeners: {
select: function(combo, records, eOpts){
//Plot the address from records[0].data.address
}
}
}
The user can press enter key to indicate that the address is either
selected or types in. Currently, the specialkey event can't always
capture it because on enter rather than taking the typed in value, it
selects the first value in store
specialkey won't be captured indeed if the dropdown list is shown. This is because the event is simulated on keydown which the dropdown list intentionally stops as can be seen here.
Still there are ways to react to user input:
Method 1
Use a custom onFieldMutation method like this:
Ext.define('MyCombo', {
extend: 'Ext.form.ComboBox',
onFieldMutation: function(e) {
if (e.getKey() === e.ENTER) {
// Call address plotting method here
}
return this.callParent(arguments);
}
});
Method 2
Use keyup:
listeners: {
keyup: {
fn: function(e) {
if (e.getKey() === e.ENTER) {
// Call address plotting method here
}
},
element: 'inputEl'
}
}
See both methods in action: https://fiddle.sencha.com/#fiddle/sdf

CKEditor widget receives data after it has been rendered

Looking at the docs you can pass startup data to a widget:
editor.execCommand( 'simplebox', {
startupData: {
align: 'left'
}
} );
However this data is pointless as there seems to be no way to affect the template output - it has already been generated before the widget's init, and also the data isn't even available at that point:
editor.widgets.add('uselesswidget', {
init: function() {
// `this.data` is empty..
// `this.dataReady` is false..
// Modifying `this.template` here does nothing..
// this.template = new CKEDITOR.template('<div>new content</div>');
// Just after init setData is called..
this.on('data', function(e) {
// `e.data` has the data but it's too late to affect the tpl..
});
},
template: '<div>there seems to be no good way of creating the widget based on the data..</div>'
});
Also adding a CKEditor tag throws a "Uncaught TypeError: Cannot read property 'align' of undefined" exception so it seems the data is also not passed to the original template:
template: '<div>Align: {align}</div>'
What is the point of having a CKEDITOR.template.output function which can accept a context, if there's no way of dynamically passing data?
The only horribly hacky solution I've found so far is to intercept the command in a beforeCommandExec and block it, then modify the template and manually execute the command again..
Any ideas to generate dynamic templates based on passed data? Thanks.
Here's how I did it.
Widget definition:
template:
'<div class="myEditable"></div>',
init: function () {
// Wait until widget fires data event
this.on('data', function(e) {
if (this.data.content) {
// Clear previous values and set initial content
this.element.setHtml('')
var newElement = CKEDITOR.dom.element.createFromHtml( this.data.content );
this.element.append(newElement,true);
this.element.setAttribute('id', this.data.id);
}
// Create nestedEditable
this.initEditable('myEditable', {
selector: '.myEditable',
allowedContent: this.data.allowedContent
})
});
}
Dynamic widget creation:
editor.execCommand('myEditable', {startupData: {
id: "1",
content: "some <em>text</em>",
allowedContent: {
'em ul li': true,
}
}});

dijit/form/Select onSelect event

Are there other events that can be registered with dojo/form/Select, except onChange?
I'd need to execute a callback function every time user selects an option, even though he selects the same option as it was selected last time. The options I have tried: onSelect, onClick did not work.
var spatialSelectionStore = new Memory({
data: [
{ label: "Rectangle", id: "RECT" },
{ label: "Polygon", id: "POLY" },
{ label: "Circle", id: "CIRC" },
{ label: "Freehand", id: "FREE" }
]
});
var os = new ObjectStore({ objectStore: spatialSelectionStore });
spatialQuerySelect = new Select({
id: "selectionType",
style: { width: "100px" },
store: os,
onChange: activateDrawTool
}, "cp_selectByShapeId");
spatialQuerySelect.startup();
I found a way to do this, and while it may not be the best way to do it, it seems to work.
I set up an aspect to fire a function after the Select._setValueAttr function executes, which is fired by the widget every time you click on either the menu drop-down or a drop-down item. Because of this, I added a check to make sure the function callback only fires when you click on a menu item (i.e. after the menu has closed). I also had to delete the onChange callback you added to Select manually, as this interfered with the aspect.
HTML
<div id="foo"></div>
JavaScript
require(["dojo/aspect", "dojo/store/Memory", "dijit/form/Select", "dojo/data/ObjectStore", "dojo/dom-construct", "dojo/dom", "dojo/aspect"], function(aspect, Memory, Select, ObjectStore, domConstruct, dom, aspect) {
var spatialSelectionStore = new Memory({
data: [
{ label: "Rectangle", id: "RECT" },
{ label: "Polygon", id: "POLY" },
{ label: "Circle", id: "CIRC" },
{ label: "Freehand", id: "FREE" }
]
});
var os = new ObjectStore({ objectStore: spatialSelectionStore });
spatialQuerySelect = new Select({
id: "selectionType",
style: { width: "100px" },
store: os
}, "cp_selectByShapeId");
spatialQuerySelect.startup();
aspect.after(spatialQuerySelect, "_setValueAttr", function() {
if(spatialQuerySelect.dropDown.isShowingNow === false) {
alert(spatialQuerySelect.get('value'));
}
});
domConstruct.place(spatialQuerySelect.domNode, dom.byId("foo"), "first");
});
Fiddle
Aspects can be very powerful, but if you use too many and rely on them too heavily, you can end up with a horrible mess of spaghetti code, so I recommend you use them sparingly, and only when necessary.
In case you're not familiar with what they do, you can tell an aspect to fire before, after, or around another method, and the aspect will "listen" to that method being fired and behave appropriately with your function callback. Further documentation.
spatialQuerySelect.dropDown.on("execute",function() {
alert(spatialQuerySelect.get('value'));
});
this would also work for all option.
onExecute: function(){
// summary:
// Attach point for notification about when a menu item has been executed.
// This is an internal mechanism used for Menus to signal to their parent to
// close them, because they are about to execute the onClick handler. In
// general developers should not attach to or override this method.
// tags:
// protected
},

How to catch imageclick event in controller of extjs4

I am working in extjs4. i have extjs view as-
Ext.define('Balaee.view.kp.Word.SearchWord', {
extend:'Ext.form.Panel',
id:'WordId',
alias:'widget.SearchWord',
bodyPadding: 30,
defaults:{
margin:'0 20 0 70'
},
layout: {
type: 'hbox'
},
items:[
{
xtype:'textfield',
fieldLabel:'Enter the Word:',
name:'Word',
// height:30,
border:false,
allowBlank:false,
emptyText: 'Enter the word',
id:'wordtext'
},
{
xtype: 'image',
width: 30,
height: 22,
src: 'http://www.asien-news.de/wp-content/uploads/new-york.jpg',
}
],});
I want to call controller's function on click of above image. So how to catch image click event in controller?
You can use listeners propetry from Observable class;
A config object containing one or more event handlers to be added to
this object during initialization. This should be a valid listeners
config object as specified in the addListener example for attaching
multiple handlers at once.
Simply bind it on the underlying element, like this:
listeners: {
el: {
click: function() {
Ext.Msg.alert("Image clicked");
}
}
}
here is a example.

Extjs create dynamic accordion using store

I'm beginning development of an app in extjs. I'm using the MVC approach, as provided in the extjs documentation.
I have some dynamic data which needs to present the user with a set of accordion controls. I've got the data in a store, but I do not know how to dynamically create the accordion items (unlike grid panels, there doesn't appear to be a store data method).
Here is my current accordion view code - with static items:
Ext.define('BP.view.induction.LeftPage', {
extend: 'Ext.Panel',
alias : 'widget.leftpage',
title: "Left Page",
layout: {
type: 'accordion',
align: 'stretch'
},
layoutConfig: {
// layout-specific configs go here
titleCollapse: true,
animate: true,
activeOnTop: true
},
items: [{
xtype: 'panel', // fake hidden panel, so all appear collapsed
hidden: true,
collapsed: false
},{
xtype: 'panel',
title: 'Panel 1',
html: 'Panel content!'
},{
xtype: 'panel',
title: 'Panel 2',
html: 'Panel content!'
},{
xtype: 'panel',
title: 'Panel 3',
html: 'Panel content!'
}]
});
Any guidance on how to achieve the above would be appreciated, thank you.
[Edit] In response to sra's request, here is my controller:
Ext.define('BP.controller.Induction', {
extend: 'Ext.app.Controller',
views: [
'induction.Binder'
],
stores: [
'Sections',
'Categories',
'Tasks'
],
init: function() {
console.log('Initialized Induction!');
}
});
I should note here that this controller loads a parent view, which in turn loads the LeftPage view - I'm not sure if this creates any scoping issues. Furthermore, as you can see, more than one store is loaded.
You can do like this (untested example with some tweaks)
Ext.define('BP.view.induction.LeftPage', {
extend: 'Ext.Panel',
alias : 'widget.leftpage',
title: "Left Page",
layout: null,
layoutConfig: null,
store: null,
attentive: true,
initComponent: function() {
var me = this;
// begin edit
// only set the store if is is not already defined
me.store = me.store ? me.store : Ext.StoreMgr.lookup('Sections'); // you may change this to any storename you want
// end edit
me.layout = { // don't set objects directly in class definitions
type: 'accordion',
align: 'stretch'
};
me.layoutConfig = { // dont set objects directly in class definitions
titleCollapse: true,
animate: true,
activeOnTop: true
};
me.callParent(arguments);
if (me.attentive) {
me.store('load', me.onRebuildContent, me);
if (me.store.count() == 0)
me.store.load();
} else {
me.buildContent();
}
},
buildContent: function() {
var me = this;
function addItem(rec) {
me.add({
xtype: 'panel',
title: rec.get('titleProperty'),
html: rec.get('bodyProprty')
});
};
me.store.each(addItem);
},
onRebuildContent: function() {
var me = this;
me.removeAll();
me.buildContent();
}
});
Your store will need at least two properties; one for the title and one for the content. And the store need to be loaded before you should instantiate this. But that can easily be done within your controller.
Edit based on comment and new OP info:
Well your view is a bit out of control of the controller. So I recommend you to simply use the StoreManager to receive a valid instance (I've edited the code, I just didn'T know the correct store to use). The StoreManager will know about the store as long as you list him within a controller (StoreManager is capable of much more, but that is all you need to know at the moment). For a further release you could also use the mixin bindable which would manage a storebinding more clean and enables you to update your accordion after the store receives new data (get updated)
Edit for readability
I've just cleaned it up a bit and included a switch param attentive which would allow you to use this component as directly bound to a store, reacting on all load events or as a sort of static one where the store should already be loaded. All in all this should give you a start without making it to complex.

Resources