Inception Genealogy – Utilities

Use of “global” functions, methods and variables is a widely discussed topic having two sides, good or evil. No matter the amount of research one does on this topic there those two sides, forever at opposition to each other. My opinion, is they are both good and a necessary evil. Good from the perspective of giving broader access to functions used repeatedly across all views and parts of the application. A “necessary” evil because not closely monitored to ensure things aren’t overwritten someplace else, naming convention is controlled to avoid the evils of global anything; they become a nightmare inside your application.

However, functions for checking session status isn’t something I want to have in every view class controller. I also don’t want to confuse view class specific items by attempting to write all functions inside a single application controller.

As programmers we have to constantly analyze our product, make decisions when something is needed in a global scope and make determination on an individual basis if global is good, evil or even a necessary evil.

ExtJS is no different than any other language in this aspect. Beginners often researching use of global’s in their app is quickly greeted by the “evil” presentation of global. Even more difficult to find is a simple solution to implement your own set of utilities accessible to every class. Convoluted implementation appears to be the name applied to the solutions you do find.

I’ve discovered including a utility class is not only going to be very powerful in Inception Genealogy, I learned a while back they are actually quite simple to implement. Rather straight forward. I feel compelled to add here many things in Ext JS are convoluted when searching for answers, made much more complex than they ever should or have to be. We as programmers seem to quickly forget the KISS principle when programmer. Simple, smaller pieces are much easier to handle than large, complex pieces. Unfortunately we never recuperate the hours we spend searching for simple answers in a convoluted environment. Or the number of times we kick ourselves because we found an agreed upon convoluted answer, implemented it and then found the simple implementation we should be using.

Creating your own utility class is simple and straight forward. In Inception Genealogy it will be beneficial and powerful for features I put in this class. Balance will be used and the process of determining if a function or variable belongs in this utility class will be monitored and questioned throughout development, working to control keeping the utility class within the scope of intended use. None of us need those monsters we find in our code whereby our design intent gets convoluted and so massive it has become difficult to manage.

Two steps are needed in creation of a utility class. First is to manually (non CMD way), create a class. Below is the code used:

 

Ext.define('inception.util', {
	singleton: true,
	alternateClassName: 'incept',

	config: {
		UID: null,
		ajaxLoadMask: null,
		ajaxCount: null,
		siteAdmin: null,
		contributor: null,
		changeTab: false
	},

	constructor: function (config) {
		this.initConfig(config);
		this.callParent(config);

		Ext.Ajax.on('beforerequest', function () {
			if (incept.getAjaxLoadMask() == null) {
                let maskWdw = Ext.create('Ext.window.Window', {
                    itemId: 'diaLoadMask',
                    title: 'Processing',
                    closable: false,
                    shadow: true,
                    border: false,
                    modal: true,
                    width: 300,
                    height: 300,
                    layout: {
                        type: 'vbox',
                        align: 'center',
                        pack: 'center'
                    },

                    items: [{
                        xtype: 'image',
                        width: 75,
                        height: 75,
                        src: '../resources/loadingSpinner.gif'
                    }, {
                        xtype: 'displayfield',
                        value: 'Processing Request...'
                    }]
                });
                incept.setAjaxLoadMask(maskWdw);
                incept.getAjaxLoadMask().show();
                incept.setAjaxCount(incept.getAjaxCount() + 1);
            } else {
                incept.setAjaxCount(incept.getAjaxCount() + 1);
            } 
		});

		Ext.Ajax.on('requestcomplete', function () {
			incept.setAjaxCount(incept.getAjaxCount() - 1);
            if (incept.getAjaxCount() <= 0) {
                Ext.ComponentQuery.query('#diaLoadMask')[0].destroy();
                incept.setAjaxLoadMask(null);
            }
            sessionStorage.setItem('sessionLastUpdate', new Date());
		});

		Ext.Ajax.on('requestexception', function () {
			Ext.ComponentQuery.query('#diaLoadMask')[0].destroy();
            incept.setAjaxLoadMask(null);		
			
            if (Ext.manifest.profile == 'classic') {
                Ext.Msg.show({
                    title: 'Unexpected Server Side Error',
                    msg: '<p>We experienced an unexpected server side error. Please try submitting your request again.</p>',
                    icon: Ext.Msg.ERROR,
                    buttons: Ext.Msg.OK
                });
            } else {
                Ext.Msg.alert('Unexpected Server Side Error', '<p>We experienced an unexpected server side error. Please try submitting your request again.</p>', Ext.emptyFn);
            }
		});
	},

	getSession: function () {
		let me = this;
		let mainPnl = Ext.ComponentQuery.query('#mainPnl')[0];
		Ext.Ajax.request({
			url: '/resources/inception.php?method=getSession',
			success: function (response) {
				let rsp = Ext.JSON.decode(response.responseText);
				me.setUID(rsp.sessionid);
				if (rsp.admin == 1) {
					mainPnl.getViewModel().set('admin', true);
					mainPnl.getComponent('tabAdmin').tab.show();
                    mainPnl.getComponent('tabUsers').tab.show();
				} else {
					mainPnl.getViewModel().set('admin', false);
					mainPnl.getComponent('tabAdmin').tab.hide();
					mainPnl.getComponent('tabUsers').tab.hide();
				}
				if (rsp.contributor == 1) {
					mainPnl.getViewModel().set('contributor', true);
					mainPnl.getComponent('tabUser').tab.show();
				} else {
					mainPnl.getViewModel().set('contributor', false);
					mainPnl.getComponent('tabUser').tab.hide();
				}
				mainPnl.getViewModel().set('userId', rsp.userId);
				mainPnl.getViewModel().set('useremail', rsp.useremail);
				mainPnl.getViewModel().set('username', rsp.username);
				mainPnl.getViewModel().set('last_acted_on', rsp.last_acted_on);
				mainPnl.getViewModel().set('loggedIn', rsp.loggedin);
				if (rsp.loggedin == 1) {
					mainPnl.getComponent('tabLogin').tab.hide();
				}
				if (rsp.userId != '') {
					mainPnl.getController().changeRoute('home');
				}
			},
			failure: function (response) {
				console.log(response);
			}
		})
	},

	/**
	 * [showError description]
	 * @param  {[string]} errType  [description]
	 * @param  {[string]} msgTitle [description]
	 * @param  {[string]} msgBody  [description]
	 * @param  {[object]} msgIcon  [Ext.Msg.ERROR, Ext.Msg.CONFIRM, Ext.Msg.INFO other types available]
	 * @param  {[object]} msgBtns  [Ext.Msg.OK, Ext.Msg.YESNO, Ext.Msg.OKCANCEL other types available]
	 * @param  {[object/fn name]} msgFn    [Ext.emptyFn, or string for the function to be called when clicking a button]
	 * @return {[boolean]}          [true]
	 */
	showError: function (errType, msgTitle, msgBody, msgIcon, msgBtns, msgFn) {
		Ext.Msg.show({
			title: msgTitle,
			msg: msgBody,
			icon: msgIcon,
			buttons: msgBtns,
			fn: msgFn
		});
		return true;
	}
})

Breaking it down, the following sets up the class to be included and allows easier access to the class when calling functions inside it.

singleton: true,
alternateClassName: 'incept',

Setting singleton: true, instantiates the class as a singleton meaning this class has one definition and loads for all other classes in the app.

Assigning alternateClassName: ‘incept’, we can now access from within all classes the showError() function by simply typing incept.showError(). Without setting alternateClassName we would access this utility class by typing inception.uitil.showError().

As to functions included currently I have getSession():

getSession: function () {
		let me = this;
		let mainPnl = Ext.ComponentQuery.query('#mainPnl')[0];
		Ext.Ajax.request({
			url: '/resources/inception.php?method=getSession',
			success: function (response) {
				let rsp = Ext.JSON.decode(response.responseText);
				me.setUID(rsp.sessionid);
				if (rsp.admin == 1) {
					mainPnl.getViewModel().set('admin', true);
					mainPnl.getComponent('tabAdmin').tab.show();
                    mainPnl.getComponent('tabUsers').tab.show();
				} else {
					mainPnl.getViewModel().set('admin', false);
					mainPnl.getComponent('tabAdmin').tab.hide();
					mainPnl.getComponent('tabUsers').tab.hide();
				}
				if (rsp.contributor == 1) {
					mainPnl.getViewModel().set('contributor', true);
					mainPnl.getComponent('tabUser').tab.show();
				} else {
					mainPnl.getViewModel().set('contributor', false);
					mainPnl.getComponent('tabUser').tab.hide();
				}
				mainPnl.getViewModel().set('userId', rsp.userId);
				mainPnl.getViewModel().set('useremail', rsp.useremail);
				mainPnl.getViewModel().set('username', rsp.username);
				mainPnl.getViewModel().set('last_acted_on', rsp.last_acted_on);
				mainPnl.getViewModel().set('loggedIn', rsp.loggedin);
				if (rsp.loggedin == 1) {
					mainPnl.getComponent('tabLogin').tab.hide();
				}
				if (rsp.userId != '') {
					mainPnl.getController().changeRoute('home');
				}
			},
			failure: function (response) {
				console.log(response);
			}
		})
	},

The intended purpose of this function is to check the users logged in and permissions on the server-side as they navigate throughout the app. My logic for handling things this way is to reduce the number of browser cookies while using server-side session variables to determine the current state of the users session and making sure their session is still active.

With a PHP backend it also to control a users session time out. PHP doesn’t automatically expire a session as deleting the sessions is up to the programmer to decide when this occurs. To increase security I want expire the session if two conditions are met, they are logged in and the last request isn’t greater than 20 minutes ago.

With this function in the utility class I can check the session and turn on/off features based on the response.

The second function showError():

showError: function (errType, msgTitle, msgBody, msgIcon, msgBtns, msgFn) {
		Ext.Msg.show({
			title: msgTitle,
			msg: msgBody,
			icon: msgIcon,
			buttons: msgBtns,
			fn: msgFn
		});
		return true;
	}

This function is intended to be the catchall for server-side errors so I’m not writing code to handle this event in every view class in the app. This will catch exceptions that aren’t related to an Ajax failure.

The config section:

config: {
		UID: null,
		ajaxLoadMask: null,
		ajaxCount: null,
		siteAdmin: null,
		contributor: null,
		changeTab: false
	},

Each object in the config automatically generates a getter and setter method. If I want to get the UID from this utility it is obtained by incept.getUID(). To set a new value for UID, incept.setUID(‘newvalue’).

This example is where we get the argument of good/evil for globals. We can change them and we might not know what is stored in the variable. However, we are programmers and as such we must be accountable to check things before changing them. These globals aren’t meant for user input, but programmatic input and changing. To me this makes the good/evil argument more about thorough/lazy programmers.

The constructor is important how it is configured.

constructor: function (config) {
		this.initConfig(config);
		this.callParent(config);

		Ext.Ajax.on('beforerequest', function () {
			if (incept.getAjaxLoadMask() == null) {
                let maskWdw = Ext.create('Ext.window.Window', {
                    itemId: 'diaLoadMask',
                    title: 'Processing',
                    closable: false,
                    shadow: true,
                    border: false,
                    modal: true,
                    width: 300,
                    height: 300,
                    layout: {
                        type: 'vbox',
                        align: 'center',
                        pack: 'center'
                    },

                    items: [{
                        xtype: 'image',
                        width: 75,
                        height: 75,
                        src: '../resources/loadingSpinner.gif'
                    }, {
                        xtype: 'displayfield',
                        value: 'Processing Request...'
                    }]
                });
                incept.setAjaxLoadMask(maskWdw);
                incept.getAjaxLoadMask().show();
                incept.setAjaxCount(incept.getAjaxCount() + 1);
            } else {
                incept.setAjaxCount(incept.getAjaxCount() + 1);
            } 
		});

		Ext.Ajax.on('requestcomplete', function () {
			incept.setAjaxCount(incept.getAjaxCount() - 1);
            if (incept.getAjaxCount() <= 0) {
                Ext.ComponentQuery.query('#diaLoadMask')[0].destroy();
                incept.setAjaxLoadMask(null);
            }
            sessionStorage.setItem('sessionLastUpdate', new Date());
		});

		Ext.Ajax.on('requestexception', function () {
			Ext.ComponentQuery.query('#diaLoadMask')[0].destroy();
            incept.setAjaxLoadMask(null);		
			
            if (Ext.manifest.profile == 'classic') {
                Ext.Msg.show({
                    title: 'Unexpected Server Side Error',
                    msg: '<p>We experienced an unexpected server side error. Please try submitting your request again.</p>',
                    icon: Ext.Msg.ERROR,
                    buttons: Ext.Msg.OK
                });
            } else {
                Ext.Msg.alert('Unexpected Server Side Error', '<p>We experienced an unexpected server side error. Please try submitting your request again.</p>', Ext.emptyFn);
            }
		});
	},

These two, in my experience must be included in the constructor for our utility class to work as expected.

this.initConfig(config);
this.callParent(config);

I don’t have an explanation why it doesn’t work without including those two items, however I do know through experience, without them it doesn’t work. So I will leave this as an unknown of Ext JS and the inner workings of CMD.

You notice some code regarding Ext.Ajax. These are included because with each Ajax request I want something to occur before, complete and any request exception. Processing every Ajax request I want to show a load mask, stopping the user from interacting with the app. Logic for making it happen on all Ajax requests is due to not wanting to develop code whereby before sending each Ajax request to make a determination if the request will have additional data required for the user. This is a genealogy program and every change, changes the data; whereby I don’t want the user interacting with the app until data is returned and the view is updated.

Ext.Ajax.on('beforerequest', function () {
			if (incept.getAjaxLoadMask() == null) {
                let maskWdw = Ext.create('Ext.window.Window', {
                    itemId: 'diaLoadMask',
                    title: 'Processing',
                    closable: false,
                    shadow: true,
                    border: false,
                    modal: true,
                    width: 300,
                    height: 300,
                    layout: {
                        type: 'vbox',
                        align: 'center',
                        pack: 'center'
                    },

                    items: [{
                        xtype: 'image',
                        width: 75,
                        height: 75,
                        src: '../resources/loadingSpinner.gif'
                    }, {
                        xtype: 'displayfield',
                        value: 'Processing Request...'
                    }]
                });
                incept.setAjaxLoadMask(maskWdw);
                incept.getAjaxLoadMask().show();
                incept.setAjaxCount(incept.getAjaxCount() + 1);
            } else {
                incept.setAjaxCount(incept.getAjaxCount() + 1);
            } 
		});

First I check to see if our utility object ajaxLoadMask is null, I then create a load mask window and apply it to the view, stopping the user interaction with the app while Ajax request processes.

Checking for a null value first is imperative as I want to ensure there is only one instance of the load mask at all times. This function also sets a counter for the number of current Ajax requests. So if the load mask variable is null we increment ajaxCount without displaying another load mask. If it is null we create a load mask and then increment ajaxCount.

Ext.Ajax.on('requestcomplete', function () {
			incept.setAjaxCount(incept.getAjaxCount() - 1);
            if (incept.getAjaxCount() <= 0) {
                Ext.ComponentQuery.query('#diaLoadMask')[0].destroy();
                incept.setAjaxLoadMask(null);
            }
            sessionStorage.setItem('sessionLastUpdate', new Date());
		});

On the request complete we first decrement ajaxCount, then check if ajaxCount == 0. If it does we destroy the load mask, then set sessionLastUpdate value in session storage. Remember I indicated I want to auto logout a user if they have been inactive for 20 minutes. This is what the session storage value will be used for in another part of the app as it evolves.

Then we have the Ajax exception because I want to show the user a graceful error when there is a true Ajax failure and not something the browser or our backend feels is best to show the user.

Ext.Ajax.on('requestexception', function () {
			Ext.ComponentQuery.query('#diaLoadMask')[0].destroy();
            incept.setAjaxLoadMask(null);		
			
            if (Ext.manifest.profile == 'classic') {
                Ext.Msg.show({
                    title: 'Unexpected Server Side Error',
                    msg: '<p>We experienced an unexpected server side error. Please try submitting your request again.</p>',
                    icon: Ext.Msg.ERROR,
                    buttons: Ext.Msg.OK
                });
            } else {
                Ext.Msg.alert('Unexpected Server Side Error', '<p>We experienced an unexpected server side error. Please try submitting your request again.</p>', Ext.emptyFn);
            }
		});

Here you see the first code indicating Inception Genealogy will be a universal app. This is checking the manifest.profile value to determine how we show an error message.

In the case of Inception Genealogy utility class we don’t need to make any other changes to app.js because of how it is defined as Ext.define(‘inception.util’). When the app was created app.js already has:

requires: [
        // This will automatically load all classes in the inception namespace
        // so that application classes do not need to require each other.
        'inception.*'
    ],

‘inception.*’ already includes all classes starting with inception. If you give your class a definition outside of the full namespace above you then would have to modify app.js to place it in the “requires” list for the application. Missing this step would then mean without it being in the requires it wouldn’t be accessible to your app anywhere.

Finally I made a change to Application.js

launch: function () {
        incept.getSession();
    },

Logic here is two fold. First I want to check the users session state when they enter the app so I can show the correct features and options based on if they are in the public domain or logged into the app.

Putting this here highlights why I want server side sessions, it will catch users who use the browsers refresh button. Without checking this on application load, if a user was logged in and then refreshed the page, they would be logged out as all Ext JS settings would be new. The server side check allows me a place to validate their login state and avoid them being logged out on refresh.

Implementation of Ext JS routing will cover keeping them on the view they are at when they refresh. Routing will be another topic another day.

I hope readers learned something from this. Most importantly how easy it is to implement something “global” for things in our apps that truly are best implemented in one location to be used all over.

This also shows, how to implement logic to every Ajax request. Something I’ve seen asked many times before and still one of those difficult to find easy answers how to implement. It is my hope by showing my full code and then break it down; users can see where and how to implement the spaghetti code we discover all over when researching.

Author: aallord

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.