toolbar

Ext JS 4: Notification Bar

I have a view in my Ext JS 4 app that has a grid panel which fills the viewport. I wanted some notification UI for success/failure upon save or other actions but not a modal dialog and not the notification window user extension (which I think looks great). Instead, I wanted a “bar” that would go across the page at the top. I’ve seen it all over the place recently. The way I ended up implementing it was to use a toolbar. This is what it looks like:

The GridBar code is pretty straightforward. I have a small animation to fade the bar in, but haven’t done anything to fade it out.

Ext.define('MyStuff.GridBar', {
	extend: 'Ext.toolbar.Toolbar',
	alias: 'widget.gridbar',
	requires: [
		'Ext.toolbar.*'
	],

	cls: 'gridbar',

	initComponent : function() {
		this.items = [
			{
				itemId: 'msg',
				xtype: 'tbtext',
				text: ''
			},
			'->',
			{
				text: 'X',
				scope: this,
				handler: this.hideBar
			}
		]

		this.callParent(arguments);
		this.msgItem = this.child('#msg');
	},

	showSuccess : function(msg) {
		this.getEl().setOpacity(0.25, false);
		this.addClass('gridbar-success');
		this.showBar(msg);
	},

	showError : function(msg) {
		this.getEl().setOpacity(0.25, false);
		this.addClass('gridbar-error');
		this.showBar(msg);
	},

	showBar : function(msg) {
		this.msgItem.update(msg);
		this.show();
		this.getEl().fadeIn({opacity: 1, duration: 1000});
		this.ownerCt.forceComponentLayout();
	},

	hideBar : function () {
		if (!this.isHidden()) {
			this.hide();
			this.ownerCt.forceComponentLayout();
			this.removeCls("gridbar-success");
			this.removeCls("gridbar-error");
			this.msgItem.update('');
		}
	},

    // Add custom processing to the onRender phase.
    afterRender: function() {
        this.callParent(arguments);
		this.hide();
    }
});

I used the following css:

.gridbar {
    margin: 3px !important;
}
.gridbar-success, .x-nlg .gridbar-success {
    border: 1px solid #AEE0AE !important;
    background-color: #D1EED1 !important;
    background-image: none !important;
}
.gridbar-error, .x-nlg .gridbar-error {
    border:  1px solid #F79897 !important;
    background-color: #FDDFDE !important;
    background-image: none !important;
}

The way to use it is to just add the GridBar into the docked items. Like this:

 {
			xtype: 'grid',
			itemId: 'myGrid',
			// more grid configs
			columns : [
				// column definitions here
			],
			dockedItems : [
				{
					itemId: 'gridBar',
					xtype: 'gridbar',
					dock: 'top'
				},
				{
					xtype: 'toolbar',
					dock: 'top',
					items: [
						// put in whatever items you have here
					]
				}
			]
		};

By default, the GridBar will be hidden on render. I had to call forceComponentLayout whenever hiding and showing the GridBar to get the UI to look correct. I tried many things to not call it, but that was the only thing that seemed to work.

Ext 4.1.0 Update
I revisited my “notification bar” with the release of Ext JS 4.1.0, and I was able to streamline it a little bit. Most importantly, I was able to remove the forceComponentLayout. Here is this code. I updated the name to be a little more generic, from GridBar to NotifyBar. Usage is the same as above.


.notifybar{
margin: 3px !important;
}
.notifybar-success, .x-nlg .notifybar-success {
border: 1px solid #AEE0AE !important;
background-color: #D1EED1 !important;
background-image: none !important;
}
.notifybar-error, .x-nlg .notifybar-error {
border: 1px solid #F79897 !important;
background-color: #FDDFDE !important;
background-image: none !important;
}
.x-toolbar .notifybar-text {
white-space: normal !important;
}

view raw

NotifyBar.css

hosted with ❤ by GitHub


/*
Copyright (c) 2013 Ann Huddleston
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
Ext.define('MyStuff.NotifyBar', {
extend: 'Ext.toolbar.Toolbar',
alias: 'widget.notifybar',
requires: [
'Ext.toolbar.*'
],
cls: 'notifybar',
initComponent : function() {
this.items = [
{
itemId: 'msg',
flex: 1,
xtype: 'tbtext',
cls: 'notifybar-text',
text: ''
},
{
text: 'X',
scope: this,
handler: this.hideBar
}
];
this.callParent(arguments);
this.msgItem = this.child('#msg');
},
showSuccess : function(msg) {
this.addClass('notifybar-success');
this.showBar(msg);
},
showError : function(msg) {
this.addClass('notifybar-error');
this.showBar(msg);
},
showBar : function(msg) {
this.msgItem.setText(msg);
this.show();
this.getEl().setOpacity(1, {
duration: 1000
});
},
hideBar : function () {
if (this.rendered && !this.isHidden()) {
Ext.suspendLayouts();
this.hide();
this.getEl().setOpacity(0.25, false);
this.removeCls("notifybar-success notifybar-error");
this.msgItem.update('');
Ext.resumeLayouts(true);
}
},
// Add custom processing to the beforeRender phase.
beforeRender: function() {
this.callParent(arguments);
this.hide();
},
onDestroy: function() {
this.getEl().stopAnimation();
this.callParent(arguments);
}
});

view raw

NotifyBar.js

hosted with ❤ by GitHub

ExtJS Paging Toolbar that can right justify

I wanted a paging toolbar that could be placed in right-side of the toolbar, allowing you to put your own buttons on the left-side. Since PagingToolbar.initComponent decides where the fill goes, I had to extend and override initComponent so that I had control over the fill. I also added my own param alignPagingRight that defaults to false. When it is set to true, it will put the paging control on the right side of the fill.


Ext.namespace("Ext.ux");
var T = Ext.Toolbar;
/**
 * @class Ext.ux.JustifyPagingToolbar
 * @extends Ext.PagingToolbar
 * <p>This just extends the standard ExtJS paging toolbar so that the paging 
 * parts of the toolbar can be aligned on the right side of the toolbar. The
 * code in initComponent is copied directly from Ext.PagingToolbar, but with 
 * some slight modifications and the addition of the alignPagingRight config
 * item</p>
 * @constructor
 * Create a new Ext.ux.JustifyPagingToolbar
 * @param {Object} config The config object
 * @xtype justifypagingtoolbar
 */
Ext.ux.JustifyPagingToolbar= Ext.extend(Ext.PagingToolbar, {

    /**
     * @cfg {Boolean} alignPagingRight
     * <tt>true</tt> to put the paging toolbar items after a fill item, and
	 * therefore on the right-side of the toolbar.  Will put the displayInfo
	 * text immediately after the paging toolbar items.  If this is false, the
	 * paging toolbar items and and user items will appear on the left-side.
     * Defaults to <tt>false</tt>.
     */
	alignPagingRight : false,
	
	initComponent : function() {
       var pagingItems = [this.first = new T.Button({
            tooltip: this.firstText,
            overflowText: this.firstText,
            iconCls: 'x-tbar-page-first',
            disabled: true,
            handler: this.moveFirst,
            scope: this
        }), this.prev = new T.Button({
            tooltip: this.prevText,
            overflowText: this.prevText,
            iconCls: 'x-tbar-page-prev',
            disabled: true,
            handler: this.movePrevious,
            scope: this
        }), '-', this.beforePageText,
        this.inputItem = new Ext.form.NumberField({
            cls: 'x-tbar-page-number',
            allowDecimals: false,
            allowNegative: false,
            enableKeyEvents: true,
            selectOnFocus: true,
            listeners: {
                scope: this,
                keydown: this.onPagingKeyDown,
                blur: this.onPagingBlur
            }
        }), this.afterTextItem = new T.TextItem({
            text: String.format(this.afterPageText, 1)
        }), '-', this.next = new T.Button({
            tooltip: this.nextText,
            overflowText: this.nextText,
            iconCls: 'x-tbar-page-next',
            disabled: true,
            handler: this.moveNext,
            scope: this
        }), this.last = new T.Button({
            tooltip: this.lastText,
            overflowText: this.lastText,
            iconCls: 'x-tbar-page-last',
            disabled: true,
            handler: this.moveLast,
            scope: this
        }), '-', this.refresh = new T.Button({
            tooltip: this.refreshText,
            overflowText: this.refreshText,
            iconCls: 'x-tbar-loading',
            handler: this.refresh,
            scope: this
        })];


        var userItems = this.items || this.buttons || [];
		
		/* override here */
		if (this.alignPagingRight) {
			userItems.push('->');
		}
		/* end override */
		
        if (this.prependButtons) {
            this.items = userItems.concat(pagingItems);
        }else{
            this.items = pagingItems.concat(userItems);
        }
        delete this.buttons;
        if(this.displayInfo){
			/* override here */
			if ( !this.alignPagingRight) {
				this.items.push('->');
			}
			/* end override */
            this.items.push(this.displayItem = new T.TextItem({}));
        }
        Ext.PagingToolbar.superclass.initComponent.call(this);
        this.addEvents(
            /**
             * @event change
             * Fires after the active page has been changed.
             * @param {Ext.PagingToolbar} this
             * @param {Object} pageData An object that has these properties:<ul>
             * <li><code>total</code> : Number <div class="sub-desc">The total number of records in the dataset as
             * returned by the server</div></li>
             * <li><code>activePage</code> : Number <div class="sub-desc">The current page number</div></li>
             * <li><code>pages</code> : Number <div class="sub-desc">The total number of pages (calculated from
             * the total number of records in the dataset as returned by the server and the current {@link #pageSize})</div></li>
             * </ul>
             */
            'change',
            /**
             * @event beforechange
             * Fires just before the active page is changed.
             * Return false to prevent the active page from being changed.
             * @param {Ext.PagingToolbar} this
             * @param {Object} params An object hash of the parameters which the PagingToolbar will send when
             * loading the required page. This will contain:<ul>
             * <li><code>start</code> : Number <div class="sub-desc">The starting row number for the next page of records to
             * be retrieved from the server</div></li>
             * <li><code>limit</code> : Number <div class="sub-desc">The number of records to be retrieved from the server</div></li>
             * </ul>
             * <p>(note: the names of the <b>start</b> and <b>limit</b> properties are determined
             * by the store's {@link Ext.data.Store#paramNames paramNames} property.)</p>
             * <p>Parameters may be added as required in the event handler.</p>
             */
            'beforechange'
        );
        this.on('afterlayout', this.onFirstLayout, this, {single: true});
        this.cursor = 0;
        this.bindStore(this.store);
	}

});

Ext.reg('justifypagingtoolbar', Ext.ux.JustifyPagingToolbar);