Fusioncube

The online journey of a technophile, by Steve Brownlee

New AjaxCFCProxy class for Sencha ExtJS

Friday
Apr 15,2011

Related Article(s): AjaxCFC ported to ExtJS

My integration of ExtJS, ajaxCFC and ColdFusion continues.

Note: This is for version 3.3.1. I’ve also been happily playing around with version 4, but until it’s officially released, I must continue building with 3.3.1.

Anyway, here’s what I was trying to solve. When I want to allow users to serach for items from a ComboBox, I configure a store, and inside the store, I configure an Ext.data.HttpProxy to hit a separate ColdFusion page that performs a query and returns the results.

Standard HttpProxy Method

var WidgetStore = new Ext.data.Store({
    proxy: new Ext.data.HttpProxy({
        url: 'liveQueries/widgets.cfm'
    }),
    reader: new Ext.data.JsonReader({
        root: 'data',
        totalProperty:'totalRecords'
    }, Widget)
});

ColdFusion ‘Live’ Query

<cfsetting enablecfoutputonly="true">

<cfparam name="query" default="">

<cfquery name="chargeCodes" datasource="#getDatasource().getName()#">
SELECT UNIQUE widget_seq_no, identifier, descr
FROM 	widgets
WHERE 	(REGEXP_LIKE(identifier,<cfqueryparam cfsqltype="cf_sql_varchar" value="^#query#">,'i')
   OR REGEXP_LIKE(descr,<cfqueryparam cfsqltype="cf_sql_varchar" value="^#query#">,'i'))
   AND widget_type_cd='SNAPPYDS'
ORDER BY identifier asc
</cfquery>

<cfscript>
jsonBean = createobject("component","ajaxCFC.JSON");
jsonEncodedCriteria = jsonBean.encode(data=chargeCodes, queryFormat="array");
writeOutput(jsonEncodedCriteria);
</coldfusion>

<cfsetting enablecfoutputonly="false">

I hate this, because I already am using my Ext.AjaxCFC class to query my ColdSpring beans asynchronously, so why do I have to set up these standalone ColdFusion pages to performs queries that should be executed in the component?

I decided to take matters into my own hands, and I have to give the ExtJS team props again, because it was easy to accomplish by extending the Ext.data.MemoryProxy class. As you can see, all I need to do is override the doRequest() method to execute Ext.AjaxCFC.request() with the data passed in during initialization.

Ext.ux.data.AjaxCFCProxy.js

Ext.ns('Ext.ux.data');

Ext.ux.data.AjaxCFCProxy = Ext.extend(Ext.data.MemoryProxy, {
   constructor : function(data){
      Ext.ux.data.AjaxCFCProxy.superclass.constructor.call(this);
      this.data = data;
   },
   doRequest : function(action, rs, params, reader, callback, scope, arg){
      this.data.data.query = params.query;
      Ext.AjaxCFC.request({
         bean : this.data.bean,
         method : this.data.method,
         data : this.data.data,
         success: function(rs) {
            var result = reader.readRecords(rs);
            callback.call(scope, result, arg, true);
         },
         error: function(results) {
            Ext.MessageBox.alert('Load Failed', 'Unable to load requested data');
         }
      });
   }
});

New and Improved Combobox for Searching

WidgetSearch = new Ext.form.ComboBox({
    minChars:           2,
    loadingText:        '',
    itemSelector:       'div.search-item',
    triggerClass:       'x-form-search-trigger',
    emptyText:          '',
    width:              300,
    listWidth:          0,
    store: new Ext.data.Store({
        // I've extended the base Ext.data.MemoryProxy()
        // class to use the AjaxCFC object for searching
        // instead of having to use a liveQuery. Much cleaner.
        proxy: new Ext.ux.data.AjaxCFCProxy({
            bean    : 'Widget',
            method  : 'search',
            data    : { source_table : 'WIDGET' }
        }),
Tuesday
Dec 7,2010

I’ve been using the AjaxCFC library for years. It’s my preferred way of integrating Javascript and ColdFusion via AJAX. I’ve even modified it from its original form so that my implementation was strictly for integration with jQuery, only returns JSON strings (ignoring WDDX and simple string), and can work with ColdSpring.

Now that I’m a heavy user of the Sencha ExtJS framework, I thought it would be useful to port the jQuery.ajaxCFC.js file over to an Ext.AjaxCFC.js file that extended the native Ext.data.Connection class and utilized the Ext.Ajax object.

Took me about half the day, but I finally got a working Ext.AjaxCFC.request() method that uses the same syntax as the $.AjaxCFC() method. For those familiar with the inner workings and code of the jQuery AjaxCFC class, this will look very familiar.

So now I can make AJAX calls using native ExtJS classes, access ColdSpring beans in my application’s bean factory, or connect directly to any CFC

Ext.AjaxCFC.js

Ext.AjaxCFCConnection = Ext.extend(Ext.data.Connection, {
   data        : null,
   queryFormat : 'array',
   factory     : (typeof(__ajaxConfig) == 'undefined') ? null : __ajaxConfig.beanFactory,
   timeout     : (typeof(__ajaxConfig) == 'undefined') ? 30000 : __ajaxConfig.defaultTimeout,
   url         : __ajaxConfig.url,
   bean        : null,

   request : function(arguments) {
      var params = (typeof(arguments.data) == 'undefined') ? {} : arguments.data;
      arguments.params = {};
      arguments.params['C0-ID']         = (Math.floor(Math.random() * 10001) + "_" + new Date().getTime()).toString(),
      arguments.params['method']        = 'init';
      arguments.params['component']     = arguments.component;
      arguments.params['bean']          = (typeof(arguments.bean) == 'undefined') ? this.bean : arguments.bean;
      arguments.params['factory']       = this.factory;
      arguments.params['C0-METHODNAME'] = arguments.method;
      arguments.params['queryFormat']   = (typeof(arguments.queryFormat) == 'undefined') ? this.queryFormat : arguments.queryFormat;
      arguments.params['C0-PARAM0']     = params;

      arguments.url = this.url + '?method=' + arguments.params['method'];
      arguments.method = 'POST';
      arguments.failure = arguments.error;
      arguments.timeout = this.timeout;

      var ____success = arguments.success;

      arguments.success = function(data) {
         data = data.responseText.replace(/^\s*|\s*$/g, '');

         if (data.substring(0,9) == '__json__:') {
            data = Ext.util.JSON.decode(data.slice(9));
         }
         ____success(data, this);
      };

      if ( params ) {
         if (typeof params != 'string') {
            arguments.params['C0-PARAM0'] = Ext.util.JSON.encode(params);
         }
      }

      Ext.Ajax.request(arguments);
   }
});

Ext.AjaxCFC = new Ext.AjaxCFCConnection();

Usage

// Include Ext.AjaxCFC code
<script type="text/javascript" src="js/Ext.AjaxCFC.js"></script>

// Default configuration properties for the ajaxCFC library
__ajaxConfig = {
   'url':'/myApp/ajaxCFC/ajax.cfc',
   'defaultTimeout':30000,
   'beanFactory':'application.beanFactory'
};

// AjaxCFC call using Ext.data.Connection class
Ext.AjaxCFC.request({
   bean: 'AColdSpringBean',
   method: 'aMethod',
   data: {
      'id': 416198,
      'first_name': 'Steve',
      'last_name': 'Brownlee'
   },
   success: function(details, s){
      DataStore.loadData(details);
   },
   error: function(results){
      Ext.MessageBox.alert('Search Failed', 'An unexpected error occurred. Please try again.');
   }
});

Fixing the Worst Hack of my Career

Tuesday
Jul 20,2010

Several years ago, I designed and developed an application whose scope was far greater than the business folks imagined. Therefore, the timeline was far more compressed than I would have liked.

I cut a lot of corners in that project in order to get it out in time, but there was only one hack that has haunted me since I reluctantly pushed it into production: I returned a large HTML string from an AJAX call and inserted it directly into the DOM.

It was far easier, at the time, to simply loop through the highly complex query structures in my class and build the HTML structure there rather than build a JSON string and use a JavaScript parsing class to build the HTML on the browser.

Well, in a little twist of fate, there was an update scheduled for this application; and this time I had some extra time as the release date got pushed back a couple of weeks. Using that to my advantage, I finally was able to fine-tune that entire mechanism, including:

  • Passing a vastly more compressed JSON string back to the interaction layer.
  • Using a new HTMLRender class in JavaScript to parse the JSON, and then build and insert the HTML into the DOM.
  • Reduced the number of hits on the database by retrieving all information in one, massive query instead of dozens of smaller ones.
  • Increased the usage of Sencha ExtJS in the layout to make it more attractive.
  • All in all, this particular section of the application now executes about 70% faster, is less resource intensive on the database, and is easier to use.

    It’s a big monkey off my back. Not to mention that I always enjoy going back to code from previous projects and seeing how I can improve upon it since I’m always learning new tricks.

  • Tuesday
    Jan 8,2008

    These three Javascript libraries are at the core of how I build fun, well-organized, and easy to maintain web applications. Each one serves a different purpose:

    • jQuery – The best at DOM selection, manipulation and searching
    • Ext – Makes all my little widgets look great and fun for the users
    • ajaxCFC – Handles calls to my business logic and serialization of data

    I’ll start off with a nice example of how these three libraries play well together and can make an otherwise complicated action very simple and easy to write.

    This code has three pieces of functionality. I’m using the jQuery Datepicker on my page, and when the user selects a date from the calendar, I want the database to immediately be updated with the date chosen. If the method fails, throw up a nice message box with a failure message and ask the user to try again.

    // Use jQuery to bind the dataSelected event of the jQuery DatePicker
    $('#requestDueDate').bind('dateSelected',function(e, selectedDate, $td){
            // Use ajaxCFC to call the updateRequestDates() function of the Request component
    	$.AjaxCFC({
    		url: "model/ajax/request/Request.cfc",
    		useDefaultErrorHandler: false,
    		method: "updateRequestDates",
    		data: { 'request_hdr_seq_no': request_hdr_seq_no,
    			'request_due_date': selectedDate.asString(),
    			'test_completion_date':$("#testCompletionDate").val() },
    		timeout:30000,
    		success: submitRequestCheck,
    		error: function(results) {
                            // If the call fails, throw up an Ext message box to alert the user
    			Ext.MessageBox.alert('Update Failed', "Failed to update the request due date. Please try again.");
    			$('input#testCompletionDate').val("");
    		}
    	});
    });

    All I was required to do was write 14 lines of code. Now, I can’t even imagine having to write this code without the use of the Big 3.

    Now for one of my favorite features of this site (it’s actually a boring feature, but how I implemented it is cool).
    Notes Accordion Dialog II

    This is just a simple note feature where users can record any pertinent information about the work they are performing. I’m using the Ext Accordion Widget to display all past notes. You can see how the user can expand and contract each note item – not really needed, but never hurts to wow the users a little.

    This particular page was written to operate as a true application. Any changes to the UI are immediately written to the database via AJAX calls. The UI is then updated when the operation completes, and the user can continue working while several tasks are being executed. No page reloads at all.

    So when they add a note, it must be inserted into the database, and the Accordian object must have a new panel added to it, with the new text inserted inside.

    I’ll admit, it stretched all my capabilities and imagination for Javascript programming. All in all, it took over 3 days of trying different things, and research, to get it to work.

    First, I had to include the required features of the libraries.

    <script type="text/javascript" src="js/jquery.AjaxCFC.js"></script>
    <script type="text/javascript" src="js/json.js"></script>
    
    <script type="text/javascript" src="js/ext-buttons.js"></script>
    <script type="text/javascript" src="js/ext-layout.js"></script>
    <script type="text/javascript" src="js/Ext.ux.InfoPanel.js"></script>
    <script type="text/javascript" src="js/Ext.ux.Accordion.js"></script>

    *This adds up to 300k of jQuery/Ext Javascript libraries.

    Then, I build the <div> element that will be used to display the dialog and the accordion panels

    <div id="NoteModalDialog">
    	<div class="x-dlg-hd">Request Notes</div>
    	<div class="x-dlg-bd">
           	<div class="x-dlg-tab" title="Notes">
    			<div id="RequestNoteForm"></div>
    			<div style="font-size:1.1em; font-weight:800;">Previous Notes</div>
    			<div id="acc-ct" style="width:100%; height:200px">
    				<cfloop query="requestNotesQuery">
    					<cfoutput>
    					<div id="panel-#currentRow#">
    						<div>
    							#first_name# #last_name# - #setup_date#:
    						</div>
    						<div>
    							<div class="text-content">#note_txt#</div>
    							<div style="border-bottom:5px solid white;"></div>
    						</div>
    					</div>
    					</cfoutput>
    				</cfloop>
    			</div>
    		</div>
    	</div>
    </div>

    Then I instantiate the accordion object and build all the panels with previous notes

    $(document).ready(function(){
    	// Create accordion to hold previous notes in note popup
    	var accordion = new Ext.ux.Accordion('acc-ct', { height:400,independent:true});
    
    	// Create a panel for each previous note
    	<cfloop query="requestNotesQuery"><cfoutput>
    		var panel#currentRow# = accordion.add(new Ext.ux.InfoPanel('panel-#currentRow#', {collapsed:false}));
    	</cfoutput></cfloop>
    }

    Then, of course, I need my wonderful Javascript object that handles building the dialog, saving the note, and loading of previous notes back into the UI/DOM.

    It first builds a dynamic Ext form (which includes validation).

    var noteForm = new Ext.form.Form({ labelWidth:25 });
    var noteText = new Ext.form.TextArea({
    	fieldLabel: 'Note',
    	name: 'noteText',
    	width:375,
    	allowBlank:false,
    	grow:true,
    	growMax:200,
    	emptyText:"Enter your request notes here..."
    });
    noteForm.add(noteText);

    Then using ajaxCFC, I insert the data into database

    $.AjaxCFC({
    	url: "model/ajax/note/Note.cfc",
    	useDefaultErrorHandler: false,
    	method: "add",
    	async:false,
    	data: { 'seq_no': request_hdr_seq_no, 'note_type':'REQUESTHDR', 'note_txt': noteText.getValue() },
    	timeout:30000,
    	success: function(results) {
    		// Reset the note field to blank
    		noteText.setRawValue("");
    
    		{{ This nested ajaxCFC call code shown below }}
    	},
    	error: function(results) {
    		Ext.MessageBox.alert('Save Failed', results.responseText);
    	}
    });

    Upon success of the insertion of the data, update the UI by adding a panel to the Accordion object with the new note inside.

    // Return all notes from the database for this request
    $.AjaxCFC({
    	url: "model/ajax/request/Request.cfc",
    	useDefaultErrorHandler: false,
    	method: "getAllRequestNotes",
    	async:false,
    	data: { 'request_hdr_seq_no': request_hdr_seq_no },
    	timeout:30000,
    	success: function(results) {
    		// Empty out the current accordion object and re-populate it with a panel for each note
    		var panel = new Object();
    		var accordion = new Ext.ux.Accordion('acc-ct', { height:'400', independent:true });
    		$("#acc-ct").empty();
    
    		for(i=0; i<results.NOTECOUNT;i++) {
    			$("#acc-ct").append('<div id="panel-' + i + '"><div>' + results.PANELS[i].FIRST_NAME + ' ' + results.PANELS[i].LAST_NAME + ' - ' +    results.PANELS[i].SETUP_DATE + '</div><div><div class="text-content">' + results.PANELS[i].NOTE_TXT + '</div></div></div>');
    			panel[i] = accordion.add(new Ext.ux.InfoPanel('panel-'+i, {collapsed:false}));
    		}
    	},
    	error: function(results) {
    		Ext.MessageBox.alert('UI Update Failed', "Failed to update Notes popup with latest note.");
    	}
    });

    I put it all together, and I get a self-documenting, elegant Javascript object that handles user notes.

    // Create the object that will be used for the Request Notes popup
    var requestNotesEdit = function() {
    	var dialog;
    	var noteForm = new Ext.form.Form({ labelWidth:25 });
    	var noteText = new Ext.form.TextArea({
    		fieldLabel: 'Note',
    		name: 'noteText',
    		width:375,
    		allowBlank:false,
    		grow:true,
    		growMax:200,
    		emptyText:"Enter your request notes here..."
    	});
    	noteForm.add(noteText);
    
    	return {
    		init : function() { this.buildDialog(); },
    		saveData : function() {
    			if (noteForm.isValid()) {
    
    				$.AjaxCFC({
    					url: "model/ajax/note/Note.cfc",
    					useDefaultErrorHandler: false,
    					method: "add",
    					async:false,
    					data: { 'seq_no': request_hdr_seq_no, 'note_type':'REQUESTHDR', 'note_txt': noteText.getValue() },
    					timeout:30000,
    					success: function(results) {
    						// Reset the note field to blank
    						noteText.setRawValue("");
    
    						// Return all notes from the database for this request
    						$.AjaxCFC({
    							url: "model/ajax/request/Request.cfc",
    							useDefaultErrorHandler: false,
    							method: "getAllRequestNotes",
    							async:false,
    							data: { 'request_hdr_seq_no': request_hdr_seq_no },
    							timeout:30000,
    							success: function(results) {
    
    								// Empty out the current accordion object and re-populate it with a panel for each note
    								var panel = new Object();
    								var accordion = new Ext.ux.Accordion('acc-ct', { height:'400', independent:true });
    								$("#acc-ct").empty();
    
    								for(i=0; i<results.NOTECOUNT;i++) {
    									$("#acc-ct").append('<div id="panel-' + i + '"><div>' + results.PANELS[i].FIRST_NAME + ' ' + results.PANELS[i].LAST_NAME + ' - ' +    results.PANELS[i].SETUP_DATE + '</div><div><div class="text-content">' + results.PANELS[i].NOTE_TXT + '</div></div></div>');
    									panel[i] = accordion.add(new Ext.ux.InfoPanel('panel-'+i, {collapsed:false}));
    								}
    							},
    							error: function(results) {
    								Ext.MessageBox.alert('UI Update Failed', "Failed to update Notes popup with latest note.");
    							}
    						});
    
    					},
    					error: function(results) {
    						Ext.MessageBox.alert('Save Failed', results.responseText);
    					}
    				});
    
    				dialog.hide();
    			}else{
    				Ext.Msg.alert('Field Required', 'Please enter in some note text before submitting.');
    			}
    		},
    		buildDialog : function() {
    			noteForm.render('RequestNoteForm');
    			dialog = new Ext.BasicDialog("NoteModalDialog", {
    				modal:true,
    			    width:450,
    			    height:350,
    			    shadow:true,
    			    minWidth:400,
    			    minHeight:300,
    			    animateTarget:'requestNotes'
    			});
    			dialog.addKeyListener(27, this.hideWindow, this);
    	        dialog.addButton('Save', this.saveData, this);
    	        dialog.addButton('Cancel', this.hideWindow, this);
    		},
    		showWindow : function() { dialog.show(); },
    		hideWindow : function() { dialog.hide(); }
    	};
    };

    About Steve

    I am a technologist, and have been ever since 1980 when I got my very first TRS-80 and programmed it to do my math homework. I love to share the gift of technology with others and show them the wonderful things it can do for them, and how they should not fear it, but embrace it.

    Latest Tweets

    • Ok... stayed up way too late trying out website designs for my wife's new nonprofit. The kids will be getting me u... — http://t.co/QrKh5iBI
    • Am I the only one who likes Google’s new privacy policy? http://t.co/qwcym5wH
    • All that time wasted learning the .NET framework - Fusioncube - http://t.co/krANoWmg
    • @marcesher libraries like Less only do what you tell them. You can make a mixin to do that, but it doesn't assume anything (which is good)
    • Circus about to start. Girls are so excited they can't stand it!!!! (with Sabrina and Tessa at @BrdgstoneArena) [pic] — http://t.co/PXwi5emj

    Subscribe

    Entries (RSS)
    Comments (RSS)