Monthly Archives: February 2014

Miscellaneous Ext JS 4 Tips and Tricks, Part I

Adding Spacing Between Action Column Icons

Our usability folks have been complaining that the default spacing (0px) between action item buttons could result in a user mistakenly clicking the wrong button.

actioncolumnitemspacing

Add this to your CSS:

.x-grid-cell-inner-action-col img {
  margin-right: 5px;
 }

Adding Tooltips to Combo Box Selections

toolkit1

Override the getInnerTpl() method as illustrated below. Don’t forget to escape embedded quotes in the output!


{
  xtype: 'combobox',
  listConfig: {
    getInnerTpl: function() {
      return "<div data-qtip='{[values.description.replace(\"'\",\"\'\")]}'>{name}</div>"; 
    }
  },
  itemId: 'toolkitfilter',
  fieldLabel: 'Toolkits',
  name: 'toolkit',
  displayField: 'name',
  forceSelection: true,
  multiSelect: true,
  queryMode: 'local',
  store: 'Toolkits',
  valueField: 'id'
}

Adding Tooltips to Form Fields

Because users need a lot of help…

Add tooltips to form fields by attaching a tooltip in the afterrender event as illustrated below:

{
  xtype: 'textfield',
  fieldLabel: 'Enter your name',
  allowBlank: false,
  anchor: '100%',
  listeners: {
   afterrender: function(component) {
     Ext.create('Ext.tip.ToolTip', {
                   target: component.getEl(),
                   html: "Enter your lastname, firstname please. This is a required field."
     });
   }
  }
}

You can try out this example at https://fiddle.sencha.com/#fiddle/3vs and check out the full application at http://www.naccho.org/toolbox

Happy coding!

This Old App: Retrofitting an Ext JS 4 GUI on top of an existing ColdFusion Infrastructure

Recently my company, Fig Leaf Software, was tasked by one of our longstanding customers to replace first-generation query-by-example search and admin tools that had been developed by a different vendor with a new modern interface that had to remain backwards-compatible with old versions of Microsoft Internet Explorer.

The existing application utilized an old version of ColdFusion (version 8), which isn’t normally a problem except that the original developer:

  1. Had no concept of encapsulation whatsoever (no ColdFusion CFC’s were used)
  2. Thought that using ColdFusion’s built-in (and now deprecated) Verity search engine, otherwise known as “Satan’s Favorite Indexer” was a great idea.
  3. Thought that not commenting your code leads to job security

The developer did implement a reasonably normalized database schema with most of the data access getting performed by SQL Server stored procedures.

Since the ColdFusion codebase could not be easily maintained, we partially redeveloped the app server layer as CORS-enabled, REST-based webservices using ASP.NET while the detailed display of records and user authentication leveraged their existing codebase, remaining in ColdFusion. We chose to use Ext JS 4 and Sencha Architect for the front-end client as Sencha’s tools have excellent support for REST and the most robust grid and form field controls of any Javascript framework.

The existing GUI suffered from a number of usability problems:

  • Every action required a full-page reload
  • The GUI split the search query across five different tabs, none of which actually interacted with each other
  • The search result is organized into a difficult to use, paginated html table that didn’t provide enough information to the user in order for them to make informed selections
  • Search results could not be customized by the end-user
  • Revising search criteria required the user to move back-and-forth between the search GUI and the results GUI

The old query-by-example interface:
Image

The old search results interface:
naccho2

Our revamped interface streamlines the search process by:

  • Keeping costs in-check by using the client’s existing database infrastructure (schema & stored procedures) and porting unstructured ColdFusion code into .NET RESTful services API.
  • Providing users with a single unified search form that reacts immediately to user input
  • Keeping the search form on the same page as the search results
  • Using the Ext JS 4 “infinity grid” feature that enables to easily and quickly browse through unlimited data volumes by loading pages of data in the background
  • Displays a full summary of matching records that enable the user to make a more informed choice about whether they want to see more detailed information
  • Enabling the end-user to reconfigure the grid by dragging-and-dropping columns as well as hide columns from view
  • Enabling more efficient keyword selection by displaying a taxonomy in a tree view
  • Replacing the unreliable Verity search engine
  • Includes a help video that is served from YouTube

The new Ext JS 4-based query-by-example GUI:

newgui

In addition to giving end-users a nice, modern, easy-to-use GUI, we also redeveloped NACCHO’s administrative interface into a modern desktop-browser based portal console. Features include:

  • Export of grid-based data into Microsoft Excel spreadsheets
  • Dynamically generated, interactive charts that can be customized and exported as PNG files
  • User-customizable grid controls
  • Single-signon security that integrates with NACCHO’s existing infrastructure

The new Admin portal:

toolboxadmin

Developing the application with Sencha Architect enabled us to react quickly to pre-launch design tweaks. Its drag-and-drop GUI and comprehensive refactoring enabled us to continually finesse and experiment with application layout and behavior.

Sencha Architect:

architect

Check out the new front-end of the app at http://www.naccho.org/toolbox and email sales@figleaf.com if you’d like to find out how we can help you breathe new life into your legacy web apps!

Ext JS 4/Ext JS 6: Printing the contents of an Ext.Panel

If you’re creating a line-of-business application, chances are pretty good that your users will need to print some data generated by your application.

printpanel

As illustrated by the following code snippet, printing the contents of an Ext 4 panel is relatively straightforward. The basic algorithm is as follows:

  1. Instantiate a hidden iframe
  2. Dynamically copy the CSS of your app into the iFrame
  3. Dynamically copy the contents of the panel into the iFrame
  4. Call the window.print() method on the iFrame
  5. Destroy the iFrame
Ext.define('MyApp.view.override.Panel', {
    override: 'Ext.panel.Panel',

    print: function(pnl) {

        if (!pnl) {
            pnl = this;
        }

        // instantiate hidden iframe

        var iFrameId = "printerFrame";
        var printFrame = Ext.get(iFrameId);

        if (printFrame == null) {
            printFrame = Ext.getBody().appendChild({
                id: iFrameId,
                tag: 'iframe',
                cls: 'x-hidden',
                style: {
                    display: "none"
                }
            });
        }

        var cw = printFrame.dom.contentWindow;

        // instantiate application stylesheets in the hidden iframe

        var stylesheets = "";
        for (var i = 0; i < document.styleSheets.length; i++) {
            stylesheets += Ext.String.format('<link rel="stylesheet" href="{0}" />', document.styleSheets[i].href);
        }

        // various style overrides
        stylesheets += ''.concat(
          "<style>", 
            ".x-panel-body {overflow: visible !important;}",
            // experimental - page break after embedded panels
            // .x-panel {page-break-after: always; margin-top: 10px}",
          "</style>"
         );

        // get the contents of the panel and remove hardcoded overflow properties
        var markup = pnl.getEl().dom.innerHTML;
        while (markup.indexOf('overflow: auto;') >= 0) {
            markup = markup.replace('overflow: auto;', '');
        }

        var str = Ext.String.format('<html><head>{0}</head><body>{1}</body></html>',stylesheets,markup);

        // output to the iframe
        cw.document.open();
        cw.document.write(str);
        cw.document.close();

        // remove style attrib that has hardcoded height property
        cw.document.getElementsByTagName('DIV')[0].removeAttribute('style');

        // print the iframe
        cw.print();

        // destroy the iframe
        Ext.fly(iFrameId).destroy();

    }
});

Once you’ve loaded the override, you can simply call the panel.print() method.

Happy coding!

*********************
* UPDATED FOR EXTJS 6
*********************

Ext.define('MyApp.view.override.Panel', {
    override: 'Ext.panel.Panel',

    print: function(pnl) {

    
        if (!pnl) {
            pnl = this;
        }

        // instantiate hidden iframe

        var iFrameId = "printerFrame";
        var printFrame = Ext.get(iFrameId);

        if (printFrame == null) {
            printFrame = Ext.getBody().appendChild({
                id: iFrameId,
                tag: 'iframe',
                cls: 'x-hidden',
                style: {
                    display: "none"
                }
            });
        }

        var cw = printFrame.dom.contentWindow;

        // instantiate application stylesheets in the hidden iframe

        var stylesheets = "";
        for (var i = 0; i < document.styleSheets.length; i++) {
            stylesheets += Ext.String.format('<link rel="stylesheet" href="{0}" />', document.styleSheets[i].href);
        }

        // various style overrides
        stylesheets += ''.concat(
            "<style>",
            ".x-panel-body {overflow: visible !important;}",
            // experimental - page break after embedded panels
            // .x-panel {page-break-after: always; margin-top: 10px}",
            "</style>"
        );

        // get the contents of the panel and remove hardcoded overflow properties
        var markup = pnl.getEl().down('.x-autocontainer-innerCt').dom.innerHTML;
       
        while (markup.indexOf('overflow: auto;') >= 0) {
            markup = markup.replace('overflow: auto;', '');
        }

        var str = Ext.String.format('<html><head>{0}</head><body><img src="resources/images/gabar-logo.gif">{1}</body></html>', '', markup);

        // output to the iframe
        cw.document.open();
        cw.document.write(str);
        cw.document.close();

        // remove style attrib that has hardcoded height property
        // cw.document.getElementsByTagName('DIV')[0].removeAttribute('style');

        // print the iframe
        cw.print();

        // destroy the iframe
        Ext.fly(iFrameId).destroy();

    }
});

Ext JS 4 Grid State Management

ExtJS enables you allow your users to reorder grid columns using drag & drop. By combining this feature with Ext JS state management, you can let users customize the look of grids and preserve those settings on a going-forward basis. However, users occasionally make mistakes, so it’s helpful to also give them the option to restore the grid columns back to their initial defaults as illustrated by the following code snippet:

Ext.application({
    name: 'Fiddle',

    launch: function() {
        // tooltips are good!
        Ext.QuickTips.init();

        // turn on state management - store state in cookies
        Ext.state.Manager.setProvider(Ext.create('Ext.state.CookieProvider'));

        var store = Ext.create('Ext.data.Store', {
            fields: ['first', 'last'],
            data: [{
                first: 'Steve',
                last: 'Drucker'
            }, {
                first: 'Dave',
                last: 'Gallerizzo'
            }, {
                first: 'Jason',
                last: 'Perry'
            }]
        });


        Ext.widget('grid', {

            renderTo: Ext.getBody(),
            store: store,
            title: 'Names',
            stateful: true,          // turn on state management for the grid 
            stateId: 'gridexample',  // give the grid instance a unique state id
            width: 300,
            height: 200,
            columns: [{
                text: 'First',
                dataIndex: 'first',
                flex: 1
            }, {
                text: 'Last',
                dataIndex: 'last',
                flex: 1
            }],
            tools: [{
                type: 'gear',
                tooltip: 'Reset Grid Layout',
                handler: function(event, toolEl, owner, tool) {
                    Ext.Msg.confirm('Reset Grid Layout', 'Are you sure that you want to reset the grid layout?',

                    function(response) {
                        if (response == 'yes') {
                            var grid = tool.up('grid');

                            // clear the state management for the grid
                            Ext.state.Manager.clear(grid.stateId);
                            
                            // repaint the grid using the hardcoded defaults
                            grid.reconfigure(grid.getStore(), grid.initialConfig.columns);
                        }
                    });
                }
            }

            ]

        });

    }
});

Run the example at https://fiddle.sencha.com/#fiddle/3kq

Happy coding!

Ext JS 4 Protip: Providing Feedback to Users of a Tabbed Form

Indicating to users that they’ve made data entry mistakes on a tabbed form is a UX challenge that can easily be overcome using Sencha’s Ext JS 4.

In the following prototype, I’ve designed a single-page app that displays a list of available forms in a grid at the top of the screen, and a tabbed data entry form (which can grow to many tabs since the form itself is assembled dynamically) that is displayed at the bottom of a “border” layout:

before

At the bottom of the form, I’ve added a “Validate” button that, when pressed, visually indicates all data input fields that failed validation on the current tab, as well as indicates other tabs where invalid data is present:

after

 

The code to implement this feature is actually quite straightforward:

// highlight all invalid fields on form
button.up('form').isValid();

// get panels in tab panel
var panels = button.up('form').query('tabpanel > panel');

// loop through each panel
for (var i=0; i<panels.length; i++) {
    
    // determine if invalid fields are present on the tab
    var invalids = panels[i].query("field{isValid()==false}");

    // set appropriate icons on the tabs
    if (invalids.length > 0) {
        panels[i].setIcon('resources/images/error.png');
    } else {
        panels[i].setIcon('resources/images/check.png');
    }
}

Happy coding!