views:

5258

answers:

3

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&amp;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.

+1  A: 

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.

Eric Ryan Harrison
A: 

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

Jay Garcia
I read the source. I know WHY it's doing it. I just disagree with this in principle. I shouldn't have to resort to ugly hacks to be able to get a value that has changed in a form field. As a serious modern framework, there should be some mechanism in place to allow me to trap this information. My concern is (and the reason for the initial tweet) is that 99% of the work I do gets done very quickly, and for that I am thankful. The remaining 1% takes (on average (as a wild guess)) 1000% more of my time.
Eric Ryan Harrison
Could you use the keyup/keydown/keypress (with enableKeyEvents set to true) listener and then check the value? Might be slightly better than the polling option.
Jason
It's a good idea, but there is no guarantee that the user will ever press a key. We might be able to guarantee that the user will at least fire a click event, though I'd have to trap it at the body level to get every possible click scenario. This is a good idea though (mouse event) and I might try it. Good idea.
Eric Ryan Harrison
It is done this way because that's how the change event is defined by the W3C. "The change event occurs when a control *loses the input focus* and its value has been modified since gaining focus. This event is valid for INPUT, SELECT, and TEXTAREA. element." (http://www.w3.org/TR/DOM-Level-2-Events/events.html)
bmoeskau
Also, regarding the "1000% more time" comment -- couldn't that be said of any framework out there? Have you ever used a framework that solved 100% of your coding problems? If so, give me the link to it ;)
bmoeskau
+2  A: 

"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.

Tewr
Thanks. This works perfectly. Now if only I could have found this in the ext documentation by myself. :DI'm marking you as the correct answer.
Eric Ryan Harrison
FYI, it's usually better to bind to the SelectionModel's change event instead of the grid's click event. That way keyboard navigation will trigger the same handler.
bmoeskau
I'm glad you got your answer, Eric!
Jonathan Julian