If you use the Ext.grid.GridPanel or descendants you might have come across this. If you have multiple columns and for some reason decide to make all your columns unhideable then you will notice how the Columns menu item does not go away, regardless of the fact all your columns are no longer manageable in that way.

I spent 5 minutes Friday pondering on this issue and wrote a fix for the Ext.grid.GridView, which is the component actually responsible for rendering the menu. The crux of the fix is:

            if(g.enableColumnHide !== false){
                if (this.cm.config.length > 0) {
                    // Build count of columns that do not have menu disabled
                    var colCount = 0;

                    for (var i = 0; i < this.cm.config.length; i++) {
                        if (this.cm.config[i].menuDisabled == false) colCount++;
                    }

                    // Build count of columns that are hideable
                    var hideCount = 0;

                    for (var i = 0; i < this.cm.config.length; i++) {
                        if (this.cm.config[i].hideable == false) hideCount++;
                    }

                    //
                    if (hideCount < colCount) {
                        this.hmenu.add('-',
                            {id:"columns", text: this.columnsText, menu: this.colMenu, iconCls: 'x-cols-icon'}
                        );
                    }
                }
            }

To prove it works here is two screenshots of before and after the fix:

Before After



And that’s basically it. Below you’ll find the full code to override the Ext.grid.GridView.

Ext.override(Ext.grid.GridView,{

    renderUI : function(){

        var header = this.renderHeaders();
        var body = this.templates.body.apply({rows:''});

        var html = this.templates.master.apply({
            body: body,
            header: header
        });

        var g = this.grid;

        g.getGridEl().dom.innerHTML = html;

        this.initElements();

        // get mousedowns early
        Ext.fly(this.innerHd).on("click", this.handleHdDown, this);
        this.mainHd.on("mouseover", this.handleHdOver, this);
        this.mainHd.on("mouseout", this.handleHdOut, this);
        this.mainHd.on("mousemove", this.handleHdMove, this);

        this.scroller.on('scroll', this.syncScroll,  this);
        if(g.enableColumnResize !== false){
            this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);
        }

        if(g.enableColumnMove){
            this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
            this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
        }

        if(g.enableHdMenu !== false){
            if(g.enableColumnHide !== false){
                this.colMenu = new Ext.menu.Menu({id:g.id + "-hcols-menu"});
                this.colMenu.on("beforeshow", this.beforeColMenuShow, this);
                this.colMenu.on("itemclick", this.handleHdMenuClick, this);
            }
            this.hmenu = new Ext.menu.Menu({id: g.id + "-hctx"});
            this.hmenu.add(
                {id:"asc", text: this.sortAscText, cls: "xg-hmenu-sort-asc"},
                {id:"desc", text: this.sortDescText, cls: "xg-hmenu-sort-desc"}
            );
            if(g.enableColumnHide !== false){
                if (this.cm.config.length > 0) {
                    // Build count of columns that do not have menu disabled
                    var colCount = 0;

                    for (var i = 0; i < this.cm.config.length; i++) {
                        if (this.cm.config[i].menuDisabled == false) colCount++;
                    }

                    // Build count of columns that are hideable
                    var hideCount = 0;

                    for (var i = 0; i < this.cm.config.length; i++) {
                        if (this.cm.config[i].hideable == false) hideCount++;
                    }

                    //
                    if (hideCount < colCount) {
                        this.hmenu.add('-',
                            {id:"columns", text: this.columnsText, menu: this.colMenu, iconCls: 'x-cols-icon'}
                        );
                    }
                }
            }
            this.hmenu.on("itemclick", this.handleHdMenuClick, this);

            //g.on("headercontextmenu", this.handleHdCtx, this);
        }

        if(g.trackMouseOver){
            this.mainBody.on("mouseover", this.onRowOver, this);
            this.mainBody.on("mouseout", this.onRowOut, this);
        }
        if(g.enableDragDrop || g.enableDrag){
            this.dragZone = new Ext.grid.GridDragZone(g, {
                ddGroup : g.ddGroup || 'GridDD'
            });
        }

        this.updateHeaderSortState();
    }

});

I’ve just finished up writing an article on implementing compression in ASP.NET web pages using nothing more than the .NET Framework on RemObjects Oxygene.

You can read it here.

So today I toyed around with the idea of loading JavaScript files on-demand rather than all at once on page load. The idea is it will not slow down page loading and also you could make it conditional so you only load JavaScript if it’s needed, say if a user makes a certain choice.

My initial effort was:

function loadScript(url)
{
	var el = document.createElement("script");

	el.type = "text/javascript";
	el.src = url;

	document.getElementsByTagName("head")[0].appendChild(el);
}

Fairly simple, create a new <script> tag, set up its attributes then append it to the <head> tag. However when I came to run it with the following code it didn’t want to play ball:

<html>
	<head>
		<title>Test</title>
	</head>
	<body>
		<script type="text/javacript" src="scriptloader.js"></script>
		<script type="text/javascript">
			alert("Start");

			loadScript("otherscript.js");

			// Call something in other script
			DoOther();

			alert("Stop");
		</script>
	</body>
</html>

I’m not sure why this didn’t work but I suspect it’s because the JavaScript file isn’t actually completely loaded by the time I call DoOther() which means DoOther() doesn’t exist yet. Thankfully trawling around online I found somebody else had solved this issue by using a callback and waiting for the script to become ready.

His code is:

/**
 * function loadScript
 * Copyright (C) 2006-2007 Dao Gottwald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Contact information:
 *   Dao Gottwald  <dao@design-noir.de>
 *
 * @version  1.6
 * @url      http://design-noir.de/webdev/JS/loadScript/
 */

function loadScript(url, callback) {
	var f = arguments.callee;
	if (!("queue" in f))
		f.queue = {};
	var queue =  f.queue;
	if (url in queue) { // script is already in the document
		if (callback) {
			if (queue[url]) // still loading
				queue[url].push(callback);
			else // loaded
				callback();
		}
		return;
	}
	queue[url] = callback ? [callback] : [];
	var script = document.createElement("script");
	script.type = "text/javascript";
	script.onload = script.onreadystatechange = function() {
		if (script.readyState && script.readyState != "loaded" && script.readyState != "complete")
			return;
		script.onreadystatechange = script.onload = null;
		while (queue[url].length)
			queue[url].shift()();
		queue[url] = null;
	};
	script.src = url;
	document.getElementsByTagName("head")[0].appendChild(script);
}

As you can see this is way more extensive and complete than my paltry effort. Plugging this into my little example we have:

<html>
	<head>
		<title>Test</title>
	</head>
	<body>
		<script type="text/javacript" src="scriptloader.js"></script>
		<script type="text/javascript">
			alert("Start");

			loadScript("otherscript.js",function() {
				// Call something in other script
				DoOther();
			});

			alert("Stop");
		</script>
	</body>
</html>

Of course, it will get to alert(”Stop”) long before it calls DoOther() due to the asynchronous nature of the call but it will be able to call DoOther() now.

With Ext if you do not specify the HTTP request type then it will attempt to make this decision for you, the Ext documentation says that if the request has no parameters it will use GET otherwise it will use POST. When you’re starting out this is fine but later on you will soon find you want to change this, especially in relation to optimization, POST is a more expensive version than GET and GET is cachable.

You can easily adapt a standard Ext.Ajax request by specifying the request method like this:

Ext.Ajax.request({
	method: "GET",
	url: "http://www.example.com/",
	...
});

Simple. The above code is nice for plain AJAX requests however for Ext.data.Store requests things are a little trickier, for that you need to specify a new proxy in the config for the store to use:

var store: new Ext.data.Store({
	autoLoad: false,
	proxy: new Ext.data.HttpProxy({
		method: "GET",
		url: "http://www.example.com/timezones.php"
	}),
	reader: new Ext.data.JsonReader(
		{root: "list", totalProperty: "count"},
		[{name: "timezone_id", type: "int"},{name: "timezone_name", type: "string"}]
	)
});

Wouldn’t the store request using GET anyhow you ask? Yes it would. However, here comes the crunch, what if you added params? This is especially so when using for example the paging toolbar which passes it’s information as such.

Hopefully this will help you in your quest to write better web applications.

Note: There is a slight caveat to using GET over POST and that is because GET makes all the params part of the URL so anything that restricts URL length will not like obscenely long lists of params. In the naming and shaming game I single IE out here.

New Blog (0)

February 10th, 2009 by Lloyd, under Uncategorized.

Finally got a new blog up and running specifically for technical stuff and not mixed with any other of my crap.

I intend to write up interesting thoughts and examples I find with code as I go about my day to day life so keep checking back and hopefully you’ll come across something at some point.