messagebox

Ext JS 4: Twitter Bootstrap Style Dismissable Alerts

I don’t know why, but I keep changing the style of notification/alert messages that I am using in my applications. Probably not a good thing to have different styles in my application, but I guess I haven’t settled on the perfect one yet.

I’m using another one I made (Notification Bar) a lot in my applications when I have a grid or panel with a toolbar. Now I wanted one that is a little more modern looking. As a result, I created a simple Ext JS 4 component with dismissable alert messages, styled like the alerts in Twitter’s Bootstrap.

First a screenshot of them all:
Dismissable Alert screenshot There are a few differences from the Bootstrap look, but I’m not a CSS guru, so I’m not sure why. In addition to Chrome and Firefox, I’ve looked at the alerts in IE6, IE7 and IE8. The ‘x’ is shifted lower in IE6 and IE7, but it’s acceptable.

The new component has some helper methods to show the different types of alerts. The alerts are dismissable. The helper methods will change the css if needed and show the alert if the alert is rendered. The “show” methods do not add the alert to your items. “Dismissing” the alert just hides it. It is not being removed from the DOM.

The code is in gist 3277584.

/* CSS originally from http://twitter.github.com/bootstrap/index.html
and modified slightly since the full bootstrap css is not included */
.alert {
padding: 8px 35px 8px 14px;
margin-bottom: 18px;
color: #c09853;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
background-color: #fcf8e3;
border: 1px solid #fbeed5;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
line-height: 18px;
}
.alert-heading {
color: inherit;
}
.alert a {
color: #c09853;
}
.alert .close {
position: relative;
top: -2px;
right: -21px;
line-height: 18px;
}
.alert-success,
.alert-success a {
color: #468847;
background-color: #dff0d8;
border-color: #d6e9c6;
}
.alert-danger,
.alert-error,
.alert-danger a,
.alert-error a {
color: #b94a48;
background-color: #f2dede;
border-color: #eed3d7;
}
.alert-info,
.alert-info a {
color: #3a87ad;
background-color: #d9edf7;
border-color: #bce8f1;
}
.alert-block {
padding-top: 14px;
padding-bottom: 14px;
}
.alert-block > p,
.alert-block > ul {
margin-bottom: 0;
}
.alert-block p + p {
margin-top: 5px;
}
.close {
float: right;
font-size: 20px;
font-weight: bold;
line-height: 18px;
color: #000000;
text-shadow: 0 1px 0 #ffffff;
opacity: 0.2;
filter: alpha(opacity=20);
}
.close:hover {
color: #000000;
text-decoration: none;
cursor: pointer;
opacity: 0.4;
filter: alpha(opacity=40);
}
button.close {
padding: 0;
cursor: pointer;
background: transparent;
border: 0;
-webkit-appearance: none;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
vertical-align: middle;
margin: 0;
}
Ext.define('Ext.ux.DismissableAlert', {
extend: 'Ext.Component',
alias: 'widget.dismissalert',
childEls: [
'btnEl', 'alertTextEl', 'alertEl'
],
renderTpl: ''.concat(
'<div id="{id}-alertEl" class="alert {alertCls}">' +
'<button id="{id}-btnEl" class="close">&times;</button>' +
'<span id="{id}-alertTextEl">{text}</span>' +
'</div>'),
initComponent: function (){
var me = this;
me.callParent(arguments);
},
beforeRender: function () {
var me = this;
Ext.applyIf(me.renderData, {
text: me.text || '&#160;',
alertCls: me.alertCls || ''
});
},
onRender: function(ct, position) {
var me = this,
btn;
me.callParent(arguments);
btn = me.btnEl;
me.mon(btn, 'click', me.onClick, me);
},
onClick: function(e) {
var me = this;
if (me.preventDefault || (me.disabled && me.getHref()) && e) {
e.preventDefault();
}
if (e.button !== 0) {
return;
}
if (!me.disabled) {
me.hide();
}
},
setText: function (text){
var me = this;
me.text = text;
if (me.rendered) {
me.alertTextEl.update(text || '');
me.updateLayout();
}
},
showAlertBox:function (text, cls) {
var me = this,
alertEl = me.alertEl,
oldCls = me.alertCls;
me.text = text;
me.alertCls = cls;
if (me.rendered) {
Ext.suspendLayouts();
alertEl.removeCls(oldCls);
alertEl.addCls(cls);
me.alertTextEl.update(text || '');
if (me.hidden) {
me.show();
}
Ext.resumeLayouts(true);
}
},
showError: function (text) {
this.showAlertBox(text, 'alert-error');
},
showSuccess: function (text) {
this.showAlertBox(text, 'alert-success');
},
showInfo: function (text) {
this.showAlertBox(text, 'alert-info');
},
showStandard: function (text) {
this.showAlertBox(text, '');
}
});

Here is an example of using the DismissableAlert. I’m including it in my view in the items config, initially as hidden. Without a width, the alert will stretch across to the max of whatever is containing it.

{
	itemId: 'myalert',
	xtype: 'dismissalert',
	width: 500,
	hidden: true
}

Below is an abbreviated version of my controller. A bunch of stuff has been cut out, so it might not completely make sense, but what matters is an example of hiding (in actionShow) and showing the alert (in myAjaxCallback).

Ext.define('My.controller.Home',{
	extend: 'Ext.app.Controller',

	views : [
		'My.view.HomeCard'
	],

	refs : [
		{ ref: 'alertBox', selector: '#myalert'}
	],

	init: function(application) {
		// control stuff here
	},

	actionShow : function() {
		var alertBox = this.getAlertBox();
		if (this.needsRefresh) {
			if (alertBox) {
				alertBox.hide();
			}
			Ext.Ajax.request({
				url: 'ajax/url/here',
				scope: this,
				callback: this.myAjaxCallback
			});
		}
	},

	myAjaxCallback : function (options, success, response) {
		var json, showError = false, alertBox;
		
		// snip
		// other codes that evals AJAX json reponse and does something w/it

		if (showError) {
			alertBox = this.getAlertBox();
			alertBox.showError("An unexpected error occurred loading your latest data.");
		}
	}
});

Version Info: Ext JS 4.1
Tested on Chrome 21, Firefox 14.0.1, IE6, IE7, IE8

Ext JS 4: When you want the message box to stop processing

As you know, the Ext JS message box does not stop the flow in your app. Here is a pattern that can be used to stop or continue the flow as needed. My example is on a listener for a grid beforeselect event.

myEventHandler : function (rowModel, rec) {
	if (this.canContinue) {
		this.canContinue = false;
		return true;
	} else {
		if (this.down('#myForm').getForm().isDirty()) {
			Ext.Msg.confirm("Save Changes?",
				"There are unsaved changes.  Save them?",
				function(buttonId) {
					if (buttonId === 'no') {
						// User wants to discard changes and continue.
						// Set the flag to true and then call the action
						// a second time.  This time the action will go thru.
						this.canContinue = true;
						rowModel.select(rec);
					} else {
						this.canContinue = false;
					}
				},
				this
			);
			return false;
		}
		return true;
	}
}

This pattern is from Condor and is posted in this forum thread: MessageBox.confirm doesnt stop in some cases