Showing your SplitButton menu on the top level button press extjs4

I just finished updating an application that contained a lot of split buttons in the toolbar, which of course contained several menus where the user could select tasks. The problem is, when the original application was released I went with the standard extjs splitbutton functionality, and my users were used to the standard menus that they find in other applications.

There’s a difference, and if you want to know what it is just hover over the menu buttons on the browser that you are using to read this.  Chances are all you have to do is click anywhere on the buttons to show the menu, right? We’ll now take a look at the extjs splitButton, what you get is a small arrow that user has to press which toggles the menu between visible / invisible.

Why not change your split buttons to give the same functionality that users are already used to, and stop testing their nerves by making the target so small the slightest shake will mean they cannot see the menu.  Fortunately you can include the following listener to add a show / hide action to the main button click.


listeners:{

'click': function(me){

me.showMenu();

}

},

What happened getForm().getValues() returns a dom error in extjs

Background:

Ok let’s assume for a minute that you have a tab panel with four tabs each containing a form. You have placed this panel inside of a window, and the goal is to have one ‘save’ button on the bottom of the window that will save all of the forms simultaneously so the user does not have to remember to save after changing data on a tab before moving to the next. This is a common and straight forward task, and I am sure that it comes up in the real world often; I’ve used this layout multiple times myself.

The Problem:

You want to read the form values and consolidate them into one call to your backend database, however, then form elements in the tab panels behind the default panel have not been rendered to the dom, therefore, you cannot access them via getValues().
You want to set the values in each form; however, you cannot set values for items that do not exist.

The Solution:

The code below will construct a button that can be placed at the ‘Window’ level and will try to get the values of all child forms contained in a tab panel inside the window.

buttons:[
    {
    text    : 'Save',
    listeners : {
        click : function() {
            // The first form is always rendered!
            var first_form_values  = first_form.getForm().getValues(false),
                // This checks the form first to see if they have been rendered
                second_form_values = !second_form.rendered ? '' : second_form.getForm().getValues(false),
                third_form_values  = !third_form.rendered  ? '' : third_form.getForm().getValues(false),
                fourth_form_values = !fourth_form.rendered ? '' : fourth_form.getForm().getValues(false);

            Ext.Ajax.request({
                url:'your_url.php?' +
                    // You need urlEncode as getValues passes
                    // back an object!
                    '&' + Ext.urlEncode(first_form_values)  +
                    '&' + Ext.urlEncode(second_form_values) +
                    '&' + Ext.urlEncode(third_form_values)  +
                    '&' + Ext.urlEncode(fourth_form_values),
                method:'POST',
                success:function () {
                    msg('','Your changes have been saved successfully!');
                },
                failure:function () {
                    msg('uh-oh','Your changes have not been saved!');
                }
            });
        }}
    }
]

That’s all there is to sending your data values from multiple forms to the server in one ajax post, but what about getting values from the server and updating your forms that reside in the tab panel?

Luckily, when an element is in the tab panel you can add an ‘activate‘ listener that is waiting for the tab and form to become visible, and the best part is it is fired every time you click on the tab, which would be useful as afterrender is called once, and that’s when the component is first rendered (as the name suggests ).
The code below will load values to your form and must be placed at the formPanel element level.

listeners   :{
                    'activate': function(e){
                        working_status_form.getForm().load({
                            url: '/getData.php',
                            failure: function(form, action) {
                                Ext.Msg.alert('uh-oh','Your data has not been loaded.');
                            }
                        });
                    }
                },

Tough coding decisions we all have to make at one time or another

One thing is for certain it’s not a perfect world, and there are imperfect solutions out there that make for a perfect user experience if you know what I mean.

Recently, I’ve had to tackle some rather esoteric IE bugs that happen only on that platform. The error in question this time comes from the extjs package and not the browser so what you get in a production environment running ext-all.js in both 4.x and 3.4 is an error in the ext-all.js javascript file.

Of course, the first thing you want to do is switch to ext-all-debug.js and check what line throws the error. You find out that it happens right around line 2921 in the setStyle function. The code is below with one addition, a try … catch block that will alert the property that the error occurred on.

setStyle : function(prop, value){
            var tmp, style;
           // alert(value);
            if (typeof prop != 'object') {
                tmp = {};
                tmp[prop] = value;
                prop = tmp;
            }
            for (style in prop) {
                value = prop[style];
	try{
					 
	style == 'opacity' ?  this.setOpacity(value) : this.dom.style[chkCache(style)] = value;
	} catch(err){
	/*// alert(value);
	   for (x in prop){
		alert('error Thrown' + x + '  -- ' ); 
		}
	   alert(err.name + ' - ' + err.number + ' - ' + err.description );*/
		return true;
	  }
            }		
            return this;
        }

Given that the error occurred in the setStyle function my first inclination was to go back through every style, and bodyStyle config option in the app and make sure that the css was correct. Next I went ahead and looked at any padding config options and corrected anything that seemed amiss. I knew approximately where the error was occurring due to the loop below but that proved little help. Once again using firebug was out of the question as this only happened on IE8/9.

Now you’ve reached the point where you decide to do the unthinkable, change the ext-all file itself to add a throw – catch around the style equals statement. Why?

  • In my case this was the last app to run 3.4 so modifying this would not impact any of the other apps that were running.
  • I had researched the extjs forums and all indications were that this was a bug in this version and since updates for 3.4 are probably not going to happen I had to act fast to resolve this issue.
  • Now onto a new challenge finding the needle in the haystack that is the minified extjs javascript file!
    I have a screen shot that shows the code after it has been changed below for your reference. To find the point where you have to change the code fire up your IDE and search for the following line:

    	G=="opacity"?this.setOpacity(H):this.dom.style[v(G)]=H
     

    Replace it with this:

    try{G=="opacity"?this.setOpacity(H):this.dom.style[v(G)]=H}catch(err){return true}}return this} 
    

    Here’s the code around it for reference:

    There are errors, damned errors and then there are the IE 80020101 errors.

    These are the errors that follow the well laid out numerical error coding convention put in place right around the time the 2600 was the hot gaming machine. You can rest assured that somewhere in a nondescript office building someone knows the meaning of 80020101, for the seasoned programmers out there it’s almost as elusive as the hit album 0u812, remember the mystery, the assumptions, the summer of 88. If you haven’t seen it or can’t get your hands on IE8 here is what you are in for.

    This error occurs in ext applications when you make an ajax request and the data that comes back is not exactly in the format that IE is looking for. In every case I have run into the code performs perfectly in Chrome and Firefox, so the development tools available in those environments will do little to help you out of this one.

    As you can imagine that leaves a lot of possibilities so here are some things to look for when troubleshooting this particular error.

    Are you loading external pages into your extjs panel? Not sure, take a look at your code for something like the following which is loading an external page into a panel:

    Ext.getCmp('some_panel').load({
    	url: '/some_external_page_with_code.cfm',
    	text:'Loading...',
    	scripts:true
    });
    

    In my instance, I noticed that the page in question contained a print page script that I picked from the internet that will pop up the print dialog, the code being below:

    <script language=" javascript">
    <!--//
      function printpage() {
      window.print();
      }
      //-->
    </script> 
    

    Now I don’t know why the

    <!--//

    was inserted into the script, but I do know that IE8 did not like the

    <!--// 

    or a coldfusion comment tag

    <!---

    that was further down in the page. Once these comments were removed from the page that extjs was loading the error disappeared.

    Now since this error is so generic it’s very likely that this was just luck, and there are other syntax mistakes in data that’s coming into your app via ajax calls that will throw this exception. If you run down any code that causes this in addition to comments in Ajax data returns then please post them here so there will be a central point for those looking for help.

    Another tool to help with error detection ( or how I was able to see my cold fusion errors without using firebug )

    Full disclosure: I was doing some maintenance work on an application that someone else coded and then yet another person maintained and ran across the following code that popped up the famous purple cold fusion errors that occur when dealing with Ajax calls from the server. The only way that I was seeing the errors before was to inspect the html tab of firebug when one of the calls failed.

    This function must be placed before Ext.onReady(function(){ .

    error_responseText = function(error_trigger, responseText){	
    	error_window = window.open("", "error_responseText", "height=700, width=1000,toolbar=no,scrollbars=yes,menubar=no");
    	error_window.document.write("<TITLE>error_responseText</TITLE>");
    	error_window.document.write(error_trigger);
    	error_window.document.write("<br><br>", responseText);
    };
    

    Now to hook up the error display you will need to add an exception listener to your store and call the function above. The code to perform this is as follows:

    exception: function(obj, type, action, opt, response, arg ){
    error_responseText('store_name_here', response.responseText);
    }
    

    The result, all of that HTML that is sent back when you get a cold fusion error is rendered in a popup window under the store name that called the broken code. One caveat, if you do not close the window after looking at an error message then the following error will not be displayed.