Related
I'm working with a lot of Kendo UI windows. Is there some way to specify default values somehow globally? Or maybe a more realistic version, can I create some parent with predefined values and then just overwrite the values I need to change?
For example, I want the same error behavior and a modal parameter for all of the windows, so I would like to do something like:
$("#parentWindow").kendoWindow({
modal: true,
error: function () {
this.close();
new Notification().error();
}
});
And then use the parent window as a base for new windows:
$("#newWindow").kendoWindow({
title: "This window should have the options (modal and error) of the parentWindow",
}).??getTheRestOfTheValuesFromParent()??;
Or rewrite some parameter:
$("#newWindow2").kendoWindow({
modal: false,
title: "A window with overwritten modal parameter",
}).??getTheRestOfTheValuesFromParent()??;
Is it somehow possible to achieve this, is there any possibility of something like C# inheritance?
Maybe it's a stupid question, but I'm not so familiar with JS.
I highly encourage you to create your own wrapper code over all - or at least those more complex - kendo widgets. My team has been doing it for years in a project we use kendo for everything and we are having very positivelly results. The main purpose is what you need: a global behaviour. If after thousand windows coded over your project, you need to change them all, just change the wrapper. It's just a simple jQuery function:
$.fn.MyWindow = function(options) {
var $target = $(this);
var widget = {
_defaultOptions: {
actions: ["Minimize", "Maximize", "Close"],
visible: false,
width: 400,
height: 400,
modal: true
},
_options: options || {},
_target: $target,
_widget: null,
_init: function() {
this._manageOptions();
this._createWidget();
return this;
},
_manageOptions: function() {
// Here you can perform some validations like displaying an error when a parameter is missing or whatever
this._options = $.extend(this._options, this._defaultOptions);
},
_createWidget: function() {
this._widget = this._target.kendoWindow(this._options).data("kendoWindow");
// Create here some behaviours that the widget doesn't haves, like closing the window when user click the black overlay
if (this._options.closeOnOverlayClick) {
$('body').off('click', '.k-overlay').on('click', '.k-overlay', function() {
this._widget.close();
}.bind(this));
}
},
Show: function(center) {
if (center) {
this._widget.center();
}
this._widget.open();
}
};
return widget._init();
};
var wnd = $("#wnd").MyWindow({
title: "My first window",
closeOnOverlayClick: true // Your own parameter
});
// Now you work with your own functions:
wnd.Show(true);
Demo.
There are so many customizations, like your own events - some of those kendo's widgets doesn't haves - etc..
I will just add that there is an article(here) about creating custom Kendo widgets where you can find more information about the specifics of different scenarios that may be implemented.
Ι had a case like yours with kendo windows, kendo grids and kendo dropdownlists. For that I created HtmlHelpers for all my elements and called them when I needed to. Since you are using kendo asp.net-mvc I would recommend to look at this way.
public static WindowBuilder GlobalKendoWindow(this HtmlHelper helper)
{
return helper.Kendo().Window()
.Draggable()
.Animation(true)
.Visible(false)
.AutoFocus(true)
.Modal(true)
.Scrollable(true)
.HtmlAttributes(new { #class = "atn-modal-container" })
.Actions(actions => actions.Minimize().Close())
.Deferred();
}
and render it in my Html like this
#(Html.GlobalKendoWindow()
.Name("addCandidateDialog")
.Title(Html.GetResource(cps, "AddCandidateDialogTitle"))
.LoadContentFrom("AddCandidate", "Candidate")
.Events(events => events.Open("athena.addCandidacy.onAddCandidateOpen").Close("athena.addCandidacy.onAddCandidateClose"))
)
I'm using qooxdoo to build a custom virtualTree, which works fine as expected until I open/close/reopen a node.
It's a mess then. I don't know which part I'm missing.
here's a code sample:
virtual tree
To reproduce, please open a node (for example "Dep1"). You can hover childrens and everything is fine. Now close the node and reopen it. Now icons get changed in node and node receives the hover effect, which is not the case when we initially opened it.
Am I missing something?
Regards
code below:
var data = {
"label": "ROOT",
"children" : [
{
"LDEP" : "Dep1",
"children" : [
{
"CVEH" : 1,
"LVEH" : "veh1_1"
},
{
"CVEH" : 2,
"LVEH" : "veh1_2"
}
]
},
{
"LDEP" : "Dep2",
"children" : [
{
"CVEH" : 3,
"LVEH" : "veh2_1"
},
{
"CVEH" : 4,
"LVEH" : "veh2_2"
}
]
},
]
};
var model = qx.data.marshal.Json.createModel(data, false);
var vtree = new qx.ui.tree.VirtualTree(model, "children", "children");
this.getRoot().add(vtree,
{
left : 100,
right : 100,
top : 50
});
vtree.set({
showTopLevelOpenCloseIcons : true,
hideRoot : true,
backgroundColor : "gray"
});
/* label options */
vtree.setLabelOptions({
converter : function(value, model)
{
if (value){
return "<b>" + model.get("LDEP") + "</b>";
}
else
{
return model.get("LVEH");
}
}
});
/*
* icon options, if a dep then return a generic symbol else return
* vehicle icon
*/
vtree.setIconPath("children");
vtree.setIconOptions({
converter : function(value, model)
{
if (value){
return "icon/22/mimetypes/text-html.png";
}
else
{
return "icon/22/mimetypes/media-image.png"
}
}
});
var delegate = {
bindItem : function(controller, item, index)
{
controller.bindDefaultProperties(item, index);
//set icon size to 24x24 for leaves
var icon = item.getChildControl("icon");
if(item.getModel().getChildren){
//dept
item.setBackgroundColor("gray");
} else {
//vehicle
item.setBackgroundColor("white");
icon.set({
width : 32,
height : 32,
scale : true,
marginTop : -4
});
}
//labels, accept html
var lbl = item.getChildControl("label");
lbl.set({
rich : true,
textColor : "black"
});
//change color on pointerin and pointerout of vehicles
if (!item.getModel().getChildren){
item.addListener("pointerover", function(){
item.getChildControl("label").fadeIn(100);
item.setBackgroundColor("blue");
item.getChildControl("label").setTextColor("orange");
});
item.addListener("pointerout", function(){
item.setBackgroundColor("white");
item.getChildControl("label").setTextColor("black");
});
}
},
/*
* sorting
*/
sorter : function(a, b){
var A = (a.getChildren? a.get("LDEP") : a.get("LVEH")).toUpperCase(),
B = (b.getChildren? b.get("LDEP") : b.get("LVEH")).toUpperCase();
return A > B ? 1 : A < B ? -1 : 0;
}
};
vtree.setDelegate(delegate);
To understand the effects you are observing, it needs understanding on how virtual widgets work in qooxdoo.
Virtual widget means that you are able do display huge amounts of data, only having a few widgets rendering the visible content. Imagine a tree with hundreds of nodes and child nodes, but you only have 10 nodes visible at one time. The virtual widget then instantiates as many real widgets as needed and re-uses those widgets to display the visible part of the tree.
The virtual tree widget, which diplays nodes an leaves of a tree, reuses the instantiated widgets by changing the model of the widget and it's appearance. This way it may happen that on user interaction, a virtual tree node is rendered by an item which formely displayed a leafe.
All this is done via the delegates bindItem member function which is called every time a real widget is re-used for a virtual item. So adding an event listener in bindItem adds subsequent more and more event listeners to a single widget tree/leaf instance, showing the effects you described.
To achieve what you want, you have to add your logic into the configureItem delegate member, which is only called once on instantiation of a tree item widget. There you have to differentiate between the item currently displaying a node or a leaf which you can simply accomplish by getting the current appearance of that item via item.getAppearance(). The result will be either virtual-tree-file for a leaf and virtual-tree-folder for a node.
The event listeners which are added for pointerover and pointerout should then add the styles needed depending on the appearance.
Note all this should better be handeled by a custom appearance theme, where you could add most styles you used based on the widgets states like hover, besides the animation for the label child control with the fading in label.
Please paste the following gist into the qooxdoo playground where I've created an example which demonstrates all the speech above:
https://gist.github.com/level420/ba4e25f98618064f91f5aa6cb6bb1124
I'd like to have multiple instances of CKEditor based on the same config settings, but with different heights. I tried setting up config with the default height, setting up the 1st instance, then overriding the height & setting up the 2nd instance:
var config = {
.....
height:'400'
};
$('#editor1').ckeditor(config);
config.height = '100';
$('#editor2').ckeditor(config);
...but I get two CKEditor instances that both have 100px height.
I also tried this:
CKEDITOR.replace('editor2',{
height: '100'
});
.. I got error messages that the instance already existed. I searched around a bit & found someone in a similar situation got advice that you have to destroy() the instance before replace(), but that seems too complicated for just setting a different initial height.
In the end I set up two different configs & copied over the toolbar_Full property:
var config1 = {
height:'400',
startupOutlineBlocks:true,
scayt_autoStartup:true,
toolbar_Full:[
{ name: 'clipboard', items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] },
{ name: 'editing', items : [ 'Find','Replace','-' ] },
{ name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
{ name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','-','BidiLtr','BidiRtl' ] },
'/',
{ name: 'links', items : [ 'Link','Unlink','Anchor' ] },
{ name: 'insert', items : [ 'Image','HorizontalRule' ] },
{ name: 'styles', items : [ 'Styles','Format','Font','FontSize' ] },
{ name: 'colors', items : [ 'TextColor','BGColor' ] },
{ name: 'tools', items : [ 'Maximize', 'ShowBlocks' ] },
{ name: 'document', items : [ 'Source' ] }
]
}
var config2 = {
height:'100',
startupOutlineBlocks:true,
scayt_autoStartup:true
};
config2.toolbar_Full = config1.toolbar_Full;
$('#editor1').ckeditor(config1);
$('#editor2').ckeditor(config2);
Is there a better way? Anything I'm missing? There's this question but they didn't post quite enough to be useful, & this very similar question hasn't been answered. Thanks!
Update:
This seems to be a timing/config handling quirk of CKEditor -- the config is read & applied later (I'm guessing after the editor's DOM framework has been set up) rather than when the editor is first instantiated.
So, any changes to the config settings made immediately after the 1st editor is instantiated with .ckeditor() are actually applied by the editor at some point in the following several milliseconds. I'd argue this isn't normal behavior, or logical.
For instance, you can get the expected behavior in my first example (overriding the config.height property after the first editor has been instantiated) to work by delaying the 2nd CKEditor instance with setTimeout(). Firefox needed ~100ms, IE needed 1ms. Wacky & wrong.
CKEditor should read the config settings when each editor is first instantiated. For now, everyone has to work around that quirk.
The easiest way to initialize two editors with custom heights is:
$('#editor1').ckeditor({ height: 100 });
$('#editor2').ckeditor({ height: 200 });
or without jQuery:
CKEDITOR.replace('editor1', { height: 100 });
CKEDITOR.replace('editor2', { height: 200 });
AFAIK it isn't possible to change editor's height on the fly.
If these methods weren't working for you, then you were doing sth else wrong.
Update:
Answering to your comment - your question in fact wasn't about CKEditor, but rather about sharing one object with only two different properties. So you can try like this:
var configShared = {
startupOutlineBlocks:true,
scayt_autoStartup:true,
// etc.
},
config1 = CKEDITOR.tools.prototypedCopy(configShared),
config2 = CKEDITOR.tools.prototypedCopy(configShared);
config1.height = 100;
config2.height = 200;
CKEDITOR.replace('editor1', config1);
CKEDITOR.replace('editor2', config2);
CKEDITOR.tools.prototypedCopy is a tool that creates new object with prototype set to the passed one. So they share all properties except of these you override later.
Update 2:
This is the update for the "Update" section in the question :).
There's no quirk in CKEditor's timing or bug or whatsoever - it's pure JavaScript and how BOM/DOM and browsers work plus some practical approach.
First thing - 90% of BOM/DOM is synchronous, but there are a couple of things that aren't. Because of this entire editor has to have asynchronous nature. That's why it provides so many events.
Second thing - in JS object are passed by reference and as we want CKEditor to initialize very quickly we should avoid unnecessary tasks. One of these is copying config object (without good reason). So to save some msecs (and because of async plugins loading too) CKEditor extends passed config object only by setting its prototype to object containing default options.
Summarizing - I know that this may look like a bug, but it's how JS/BOM/DOM libs work. I'm pretty sure that many other libs' async methods are affected by the same issue.
Add this you will get the different toolbar for both CKeditor in single page
<script>
CKEDITOR.on('instanceCreated', function (event) {
var editor = event.editor,
element = editor.element;
if (element.is('h1', 'h2', 'h3') || element.getAttribute('id') == 'editorTitle') {
editor.on('configLoaded', function () {
// Remove unnecessary plugins to make the editor simpler.
editor.config.removePlugins = 'find,flash,' +
'forms,iframe,image,newpage,removeformat,' +
'smiley,specialchar,stylescombo,templates';
// Rearrange the layout of the toolbar.
editor.config.toolbarGroups = [
{ name: 'editing', groups: ['basicstyles'] },
{ name: 'undo' },
//{ name: 'clipboard', groups: ['selection', 'clipboard'] },
{ name: 'styles' },
{ name: 'colors' },
{ name: 'tools' }
// { name: 'about' }
];
});
}
});
</script>
Solution above from Reinmar is working for me, however I decided to give 1 more solution that i used before this one.
It's really simple, all you need to know is that ckeditor create content div element for every instance with almost the same id, only difference is incremental value. So if you have 2,3,4.. instances only difference will be ordinal number. Code is here:
CKEDITOR.on('instanceReady', function(){
$('#cke_1_contents').css('height','200px');
});
This event will be activated for every instance you have, so if you want to set height for all instances you could create global variable and use it like x in #cke_"+x+"contents, every time event is activated increase x for 1, check which instance in row is with simple if and then set height.
var x=1;
CKEDITOR.on('instanceReady', function(){
if(x==1) h='200px';
else if(x==2)h='400px';
else if(x==3)h='700px';
$('#cke_'+x+'_contents').css('height',h);
x++;
});
This is not best solution but it is working, problem is you actually see content div resizing.
Update 25 Jun 2019.
Please Use this code to add multiple CKEditor instances with custom height for each one. Easiest way ever.
<textarea name="editor1" style="height:30px;" class="ckeditor"></textarea>
<script type="text/javascript">
CKEDITOR.replace( 'editor1' );
CKEDITOR.add
</script>
<textarea name="editor2" style="height:40px;" class="ckeditor"></textarea>
<script type="text/javascript">
CKEDITOR.replace( 'editor2' );
CKEDITOR.add
</script>
<textarea name="editor3" style="height:50px;" class="ckeditor"></textarea>
<script type="text/javascript">
CKEDITOR.replace( 'editor3' );
CKEDITOR.add
</script>
<textarea name="editor4" style="height:60px;" class="ckeditor"></textarea>
<script type="text/javascript">
CKEDITOR.replace( 'editor4' );
CKEDITOR.add
</script>
<textarea name="editor5" style="height:70px;" class="ckeditor"></textarea>
<script type="text/javascript">
CKEDITOR.replace( 'editor5' );
CKEDITOR.add
</script>
Ref: Here
If you add the ckeditor.js to page more than once too, it may cause that problem.
The script code must be defined once in every page.
<script type="text/javascript" src="Fck342/ckeditor.js"></script>
just use CKEDITOR.replaceAll();
Okay, so in a nutshell, what I need to do is to automatically apply a set of sorting criteria and data filters to the jqGrid when it loads. The intent is that the user will start with about 10 pre-filled filters and then, if they so choose, they can alter those filters or the sorting however they see fit.
So far, with much Google-ing, trial and error and sweat, I have the following working:
-> I can load/save the sort column & sort order in a session cookie.
-> I can load the search dialog with pre-defined search filters. After the grid loads, I can open the modal dialog and see the proper filters and if I click "Find" the appropriate data is posted to the server and the right results are returned to the screen.
The thing that is biting me in the butt right now is, I think, the easy part, but it escapes me. I can't seem to do either of the following:
( A ) The ideal thing would be if I could attach these filters to the grid and it's post data in advance of the initial load so that there is only a single trip to the server.
( B ) The workable solution, though less ideal, would be for the grid to load the first page of the unfiltered data first, and then apply the filters and re-query the server for the filtered data.
Since I can manually click the "find" button today and it works, I thought that "B" would be a good next-step. So, in my gridComplete function, I have the following code:
$('#AccountGrid').clearFilter({gridName:'AccountGrid', pagerName:'AccountPager'});
$('#AccountGrid').addFilter({gridName:'AccountGrid', field:'AccountID', data:1, op:'ne'});
$('#AccountGrid').addFilter({gridName:'AccountGrid', field:'AccountID', data:3, op:'ne'});
// $('#fbox_AccountGrid').searchFilter().search();
// $('#fbox_AccountGrid .ui-search').click();
$('#AccountGrid').trigger('reloadGrid');
NOTE: "clearFilter" and "addFilter" are extension functions I have added to jqGrid to simplify adding and removing filters on the grid. They work exactly as desired at this point.
As you can see with those last three lines of code, I have tried using the built-in function, as well as going after the find button directly and even just forcing the entire grid to refresh. Either way, there is no attempt by the grid to go get new data (I am using Fiddler to watch for it).
What am I doing wrong in trying to force the grid to reload with the new filters?
And, if you know how, can you give me some direction on how to get the initial load of the grid to respect these filters?
TIA
Tony
Here is the full grid configuration (minus the extra columns and some colModel "cruft"):
jQuery('#AccountGrid').jqGrid({
url: '<my URL>',
width: 950,
height: 330,
shrinkToFit: 'true',
datatype: 'json',
mtype: 'POST',
multiselect: true,
multiboxonly: true,
multiselectWidth: 20,
colNames: [
'Account ID'
],
colModel: [
{ name: 'AccountID', index: 'AccountID', sortable: false, hidden:false, search:true }
],
gridComplete: function () {
// add the search criteria to the grid
if (initialLoad == true){
$('#AccountGrid').clearFilter({gridName:'AccountGrid', pagerName:'AccountPager'});
$('#AccountGrid').addFilter({gridName:'AccountGrid', field:'AccountID', data:1, op:'ne'});
$('#AccountGrid').addFilter({gridName:'AccountGrid', field:'AccountID', data:3, op:'ne'});
// $('#fbox_AccountGrid').searchFilter().search();
// $('#fbox_AccountGrid .ui-search').click();
$('#AccountGrid').trigger('reloadGrid');
initialLoad = false;
}
},
jsonReader: {
repeatitems: false,
id: 'AccountID'
},
pager: jQuery('#AccountPager'),
rowNum: 50,
rowList: [10, 15, 25, 50, 75, 100],
onSortCol : function (sortname, indexColumn, sortorder){
$.cookie('AccountGrid_sortname', sortname);
$.cookie('AccountGrid_sortorder' , sortorder);
},
sortname : $.cookie('AccountGrid_sortname') ? $.cookie('AccountGrid_sortname') : 'AccountID',
sortorder: $.cookie('AccountGrid_sortorder') ? $.cookie('AccountGrid_sortorder') : 'asc',
viewrecords: true,
imgpath: ''
});
$('#AccountGrid').jqGrid('navGrid','#AccountPager',
{ view: false, add: false, edit: true, del: false,
alertcap:'No Account Selected',
alerttext: 'Please select an Account from the grid before performing this operation.',
editfunc: showAccountEditDialog },
{}, // default settings for edit
{}, // default settings for add
{}, // delete
{closeOnEscape: true, multipleSearch: true, closeAfterSearch: true }, // search options
{}
);
And, by request, here is the code I have for add/clear filter:
/*
This is a grid extension function that will insert a new filter criteria
on the specified grid with the provided field, operation & data values
*/
(function ($) {
jQuery.jgrid.addSearchFilter =
{
// get/set the parameters
addFilter: function (options) {
var grid = $(this);
// get offset values or assign defaults
var settings = $.extend({
gridName: '',
field: '',
data: '',
op: ''
}, options || {});
// get the column model object from the grid that matches the provided name
var colModel = grid.getGridParam('colModel');
var column;
for (var i = 0; i < colModel.length; i++) {
if (colModel[i].name == options.field){
column = colModel[i];
break;
}
}
colModel = null;
if (column){
// if the last filter has a value, we need to create a new one and not overwrite the existing ones
if ($('#fbox_' + options.gridName + ' .sf .data input').last().val()){
$('#fbox_' + options.gridName).searchFilter().add();
}
// assign the selections to the search dialog
$('#fbox_' + options.gridName + ' .sf .fields select.field').last().val(column.index).change();
$('#fbox_' + options.gridName + ' .sf .data input').last().val(options.data);
$('#fbox_' + options.gridName + ' .sf .ops select.default').last().val(options.op).change();
}
}
}
})(jQuery);
jQuery.fn.extend({ addFilter: jQuery.jgrid.addSearchFilter.addFilter });
/*
This is a grid extension function that will clear & reset the filter criteria
*/
(function ($) {
jQuery.jgrid.clearSearchFilter =
{
// get/set the parameters
clearFilter: function (options) {
var grid = $(this);
// get offset values or assign defaults
var settings = $.extend({
gridName: '',
pagerName: ''
}, options || {});
// clear the filters and "pop" the dialog to force the HTML rendering
$('#fbox_' + options.gridName).searchFilter().reset();
$('#' + options.pagerName + ' .ui-icon-search').click();
$('#fbox_' + options.gridName).searchFilter().close();
}
}
})(jQuery);
jQuery.fn.extend({ clearFilter: jQuery.jgrid.clearSearchFilter.clearFilter });
First of all because you don't post the code which define the jqGrid I make some assumption myself. I try to base on indirect information from your question.
1) I suppose that you use server side datatype parameter of jqGrid like 'json' or 'xml'.
2) You don't use loadonce:true parameter. In general if would be possible to make server reload from the grid having loadonce:true, but in the case you have to reset the value of datatype parameter to initial value (one from the value 'json' or 'xml').
The following three old answer: this (in case of single value searching) and this (in case of advanced searching or the toolbar searching with additional parameter stringResult:true) will give you enough information about setting the searching filters and reloading the grid corresponds to the new filters. Another answer shows how to clear the searching filter if it is no more needed.
Loading of the grid at the first time with the filters is another question. It can be very easy implemented. You should just use datatype:"local" instead of datatype:"json" or datatype:"xml" which you really need. In the case the url parameter of jqGrid will be just ignored at the first load and jqGrid send no request to the server. Then you set the filters like you as need and only then use $("#youGridId").trigger("reloadGrid");
The reason why the reloadGrid didn't work in your case I could not know exactly, but I suppose that you didn't set the search:true parameter of the jqGrid which one confuses frequently with the _search property of the postData parameter (see here).
I was asked to post this as a question on StackOverflow by http://twitter.com/jonathanjulian which was then retweeted by several other people. I already have an ugly solution, but am posting the original problem as requested.
So here's the back story. We have a massive database application that uses ExtJS exclusively for the client side view. We are using a GridPanel (Ext.grid.GridPanel) for the row view loaded from a remote store.
In each of our interfaces, we also have a FormPanel (Ext.form.FormPanel) displaying a form that allows a user to create or edit records from the GridPanel. The GridPanel columns are bound to the FormPanel form elements so that when a record is selected in the GridPanel, all of the values are populated in the form.
On each form, we have an input field for the table row ID (Primary Key) that is defined as such:
var editFormFields = [
{
fieldLabel: 'ID',
id: 'id_field',
name: 'id',
width: 100,
readOnly: true, // the user cannot change the ID, ever.
monitorValid: true
} /* other fields removed */
];
So, that is all fine and good. This works on all of our applications. When building a new interface, a requirement was made that we needed to use a third-party file storage API that provides an interface in the form of a small webpage that is loaded in an IFrame.
I placed the IFrame code inside of the html parameter of the FormPanel:
var editForm = new Ext.form.FormPanel({
html: '<div style="width:400px;"><iframe id="upload_iframe" src="no_upload.html" width="98%" height="300"></iframe></div>',
/* bunch of other parameters stripped for brevity */
});
So, whenever a user selects a record, I need to change the src attribute of the IFrame to the API URL of the service we are using. Something along the lines of http://uploadsite.com/upload?appname=whatever&id={$id_of_record_selected}
I initially went in to the id field (pasted above) and added a change listener.
var editFormFields = [
{
fieldLabel: 'ID',
id: 'id_field',
name: 'id',
width: 100,
readOnly: true, // the user cannot change the ID, ever.
monitorValid: true,
listeners: {
change: function(f,new_val) {
alert(new_val);
}
}
} /* other fields removed */
];
Nice and simple, except that it only worked when the user was focused on that form element. The rest of the time it failed to fire at all.
Frustrated that I was past a deadline and just needed it to work, I quickly implemented a decaying poller that checks the value. It's a horrible, ugly hack. But it works as expected.
I will paste my ugly dirty hack in an answer to this question.
"The GridPanel columns are bound to
the FormPanel form elements so that
when a record is selected in the
GridPanel, all of the values are
populated in the form."
As I understand it from the quote above, the rowclick event is what actually triggers the change to your form in the first place. To avoid polling, this could be the place to listen, and eventually raise to your custom change event.
Here is the ugly hack that I did to accomplish this problem:
var current_id_value = '';
var check_changes = function(offset) {
offset = offset || 100;
var id_value = document.getElementById('id_field').value || '';
if ( id_value && ( id_value != current_id_value ) ) {
current_id_value = id_value;
change_iframe(id_value);
} else {
offset = offset + 50;
if ( offset > 2500 ) {
offset = 2500;
}
setTimeout(function() { check_changes(offset); }, offset);
}
};
var change_iframe = function(id_value) {
if ( id_value ) {
document.getElementById('upload_iframe').src = 'http://api/upload.php?id=' + id_value;
} else {
document.getElementById('upload_iframe').src = 'no_upload.html';
}
setTimeout(function() { check_changes(100); }, 1500);
};
It's not pretty, but it works. All of the bosses are happy.
If you took a moment to read the source, you would see that the Ext.form.Field class only fires that change event in the onBlur function