Adding grid totals to panel titles using bootstrap-ext Badges in Extjs 5’s viewModel store


Recommended Book

One of the hidden advantages to using the bootstrap-ext theme is the ability to tap into all of the components that come with Bootstrap. One of the ways that I like to highlight the number of records in a grid is a nice clean badge with the total, or better yet on a button; say an inbox button might have a nice badge that shows the number of new items. You can read more about the badge CSS here http://getbootstrap.com/components/#badges .

In Extjs5 to get the badge to show up in your Panel title from a store in the viewModel just add the following load listener and make sure the panel has a reference that you can use like the one below:

coolWidgets:{
   model   : myapp.model. CoolWidgets,
   listeners:{
       load: function(object){
          object.$binding.owner._view.lookupReference('_someCoolGrid').setTitle(' Cool Things <span class="badge">' + object.getCount() + '</span>' );
       }
   }
...

As you can see it would be just as easy to use the button’s setText() method and add the badge there.

Extjs 5 CSS Card Flip effect on dataview xtemplate


Recommended Book

The data view xtemplate in extjs is a perfect place to utilize the CSS card flip effect which allows you to place data on the back side of a div – which maximizes screen real-estate. The example below assumes that you are using the bootstrap-ext theme and that you are using extjs5. The example below works in IE10, and 11 as well as Chrome, Firefox and Safari – it does not work in older versions of IE as it relies on CSS3. The inspiration for this feature was a post by David Walsh which can be found at http://davidwalsh.name/demo/css-flip.php.

The extjs foundation:

You will need a dataview (http://docs.sencha.com/extjs/5.0/apidocs/#!/api/Ext.view.View ), as well as a store that will contain the data you wish to display. In the tpl config of the dataview you will need to add the following DIV tags at a minimum:

<div class="flip-container">
	<div class="flipper" style="display:table;" >
		<div class="front" style="display:table-cell;">

		</div>
                <div class="back" style="display:table-cell;">

                </div>
         </div>
</div>

To provide the user with a means of flipping the card you will have to add at least one element to the front and back containers that contain the class : rotate-card, an example that takes advantage of the bootstrap glyphicons and color scheme is provided below.

<div class="rotate-card circle text-center pull-right"  >
    <span class=\'rotate-card glyphicon glyphicon-file\'  ></span>
</div>

Next, because you probably want to use the required itemSelector property for something other than flipping a div around , to provide the click functionality to the a circle above listener has to be added at the dataview level, an example that matches the div tags above is below.

…
listeners:{
        afterrender   :{
            fn       : function(view){
                var someNodes = Ext.fly(view.id);
                someNodes.on('click', function(event, t) {
                    event.stopEvent();
                    Ext.fly(t).parent('.flipper').toggleCls('flip');
                }, null, {delegate: '.rotate-card'});
            }
        }
    }
…

The CSS

.flip-container{
    -webkit-flex: 1; /* Safari 6.1+ */
    -ms-flex: 1; /* IE 10 */
    flex: 1;
}
/* entire container, keeps perspective */
.flip-container {
    -webkit-perspective: 1000;
    -moz-perspective: 1000;
    -o-perspective:1000;
    -ms-perspective: 1000;
    perspective: 1000;

    -ms-transform: perspective(1000px);
    -moz-transform: perspective(1000px);
    -moz-transform-style: preserve-3d;
    -ms-transform-style: preserve-3d;

}

/* for IE*/
.flip .back {
    -webkit-transform: rotateY(0deg);
    -moz-transform: rotateY(0deg);
    -o-transform: rotateY(0deg);
    -ms-transform: rotateY(0deg);
    transform: rotateY(0deg);
}

.flip .front{
    -webkit-transform: rotateY(180deg);
    -moz-transform: rotateY(180deg);
    -o-transform: rotateY(180deg);
    transform: rotateY(180deg);
}

/* END: for IE */

.flipper {
    -webkit-transition: 1.0s;
    -webkit-transform-style: preserve-3d;
    -ms-transition: 1.0s;

    -moz-transition: 1.0s;
    -moz-transform: perspective(1000px);
    -moz-transform-style: preserve-3d;
    -ms-transform-style: preserve-3d;

    transition: 1.0s;
    transform-style: preserve-3d;

    position: relative;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    -webkit-transition: all 1.0s ease-in-out;
    -moz-transition: all 1.0s ease-in-out;
    -o-transition: all 1.0s ease-in-out;
}

.front, .back {
    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
    -ms-backface-visibility: hidden;
    backface-visibility: hidden;

    -webkit-transition: 0.6s;
    -webkit-transform-style: preserve-3d;

    -moz-transition: 0.6s;
    -moz-transform-style: preserve-3d;

    -o-transition: 0.6s;
    -o-transform-style: preserve-3d;

    -ms-transition: 0.6s;
    -ms-transform-style: preserve-3d;

    transition: 0.6s;
    transform-style: preserve-3d;

    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;

}

.front {
    -webkit-transform   : rotateY(0deg);
    -ms-transform       : rotateY(0deg);
    background-position : center center;
    z-index             : 2;

}

.back {
    -webkit-transform: rotateY(-180deg);
    -moz-transform: rotateY(-180deg);
    -o-transform: rotateY(-180deg);
    -ms-transform: rotateY(180deg);
    transform: rotateY(-180deg);
}
/*
.rotate-3d {
    -webkit-transform: rotateY(180deg);
    -moz-transform: rotateY(180deg);
    transform: rotateY(180deg);
}*/

.circle {
    border-radius: 50%;
    width: 30px;
    background-color: #5cb85c;
    border: 1px solid #52A052;
    height: 30px;
    cursor: pointer;
}

.circle .glyphicon{
    color: #FFF;
    font-size: 15px;
    padding: 6px 0px 0px 3px;
}

.circle:hover{
    background-color: #52A052;
}

.front  {position: relative;}
.back       {position: absolute; top: 0; left: 0; width: 100%; height: 100%}

The complete dataview example is below:

{
    xtype       : 'dataview',
    flex        : 1,
    itemSelector: 'span.glyphicon-trash',
    bind :{
        store : '{cars}'
    },
    listeners:{
        itemclick   : {
            fn      : 'onDeleteFastCar',
            scope   : 'controller'
        },
        afterrender   :{
            fn       : function(view){
                var someNodes = Ext.fly(view.id);
                someNodes.on('click', function(event, t) {
                    event.stopEvent();
                    Ext.fly(t).parent('.flipper').toggleCls('flip');
                }, null, {delegate: '.rotate-card'});
            }
        }
    },
    emptyText   : '<div style="width:95%" class="center-block"><div class="alert alert-warning" role="alert"><strong>There are no fast cars here.</strong></div></div>',
    autoScroll  : true,
    tpl : [
        '<div style="width:100%; padding:5px;">',
        '<tpl for=".">',
            // Required outer container of the card object
            '<div class="flip-container">',
            // Required
            '<div class="flipper" style="display:table;" >',

            // Front card information, this is visible when the
            // dataview is populated.

            '<div class="front" style="display:table-cell;">',
                    // Using the Bootstrap panel that is provided in the bootstrap-ext theme
                    '<div class="panel panel-default" > ',
                        '<div class="panel-heading"><h4>{car_model} / {car}</h4></div>',
                        '<div class="panel-body">',
                                    '{car_description}</br>',
                            // Required tag with the class 'rotate-card' so
                            // that the user can click and change from front to
                            // back.
                            '<div class="rotate-card circle text-center pull-right"  >',
                                '<span class=\'rotate-card glyphicon glyphicon-file\'  ></span>',
                            '</div>',
                        '</div>',
                    '</div>',
            '</div>',

            // Back card information, this is visible when the
            // dataview is populated.

            '<div class="back" style="display:table-cell;">',
            '<div class="panel panel-default" > ',
                    '<div class="panel-heading">',
                        '<h4>Models in Stock Near You,
                            '<div class="pull-right">',
                                '<span class=\'glyphicon glyphicon-trash\' style=\'cursor:pointer;color:#FFF;padding-right:5px;\'></span>',
                            '</div>',
                        '</h4>',
                    '</div>',
                    '<div class="panel-body">',
                        '<strong>{location} :</strong>{vin_number}</br>' ,
                        // Required tag with the class 'rotate-card' so
                        // that the user can click and change from front to
                        // back.
                        '<div class="rotate-card circle text-center pull-right"  >',
                            '<span class=\'rotate-card glyphicon glyphicon-retweet\'  ></span>',
                        '</div>',
                    '</div>',
            '</div>',
            '</div>',
        '</div>',
        '</div>',
        '</tpl>',
        '</div>'
    ]
}

Extjs 5 Action Column item viewController handler binding

After spending a good half an hour setting up an itemclick event on the parent grid and getting the action column to call the event from the handler I walked over to boast about my success with another developer on the team and before I could explain I got a quick:

“Yeah isn’t it awesome you just have to pass the event name in the handler config.”

“What!”

That’s right, in Extjs 5 you can call an event in the view controller as simple as this:

.....
columns: [
    {
        xtype:'actioncolumn',
        width:80,
        items: [{
                    glyphCls    : 'glyphicon-file',
                    tooltip     : 'View File',
                    handler     : 'onViewFile'
               },{
                    glyphCls    : 'glyphicon-pencil',
                    tooltip     : 'Edit File',
                    handler     : 'onEditFile'
               },{
                    glyphCls    : 'glyphicon-trash',
                    tooltip     : 'Delete File',
                    handler     : 'onDeleteFile'
              }]
    },{text : 'Title',dataIndex   : 'title', flex  : 1}
]
.....

The functions mapped to the handlers all receive the following parameters:

    onEditFile : function(view, rowIdx, colIdx, item, e, record, row){
        console.log(record);
    },

View Model Data Binding – When store.load() gives you an ext-empty-store error

I’ve been starting a new enterprise application using Extjs5 and wanted to implement the MVVM pattern to include the new view Model, this is a list of the things I have learned so far and the obstacles that I have overcome.

Scenario: Create a store and load it with a parameter from the view Model’s data section

One of the things that I wanted to do was create a panel that would be created and added to a tab panel on a row click. The panel would show specific data for that row’s corresponding record. The new design pattern is perfect for this because the copy of the store can reside in the view Model and I can open as many new tabs as I want.

Obstacles:

I originally thought that I would just create the store in the view model and do a load on it when the panel was added to the parent container using Ext.getStore().load(), however, I kept getting the error “Cannot modify ext-empty-store” which was frustrating to say the least. It appears that to do the binding correctly the order in which objects are created means that the store may not exist for the few milliseconds in which you come along trying to reload it, what is there is a phantom store.

Solution:
In the view model I added a store like this:

stores: {

        myStore: {
    	    // Just holds the fields
            model   : 'myapp.model.MyStoreModel',
            proxy: {
                type : 'ajax',
                url  : '/someurl.php',
                extraParams: {
                    id: '{id}'
                },
                reader: {
                    type            : 'json',
                    rootProperty    : 'data',
                    totalProperty   : 'results'
                }
            },
            autoLoad:true
        }
}

Notice that there is an extraParams declaration with a key value pair named id. The value is in handlebars so it would be replaced by the id value in data. Since I do not know which record the user is going to click yet I can’t provide the data config to the view model yet.

In the panel itself I add the following code that will call a function called onFormAdd when the added event of the form is called.

listeners :{
scope : 'controller',
added : 'onFormAdd'
}

I also specify that the grid is going to bind to the store in the view model. The complete code for the child grid is:

{
                    xtype    : 'grid',
                    flex        : 1,
                    title       : 'My Grid',
                    bind :{
                        store : '{myStore}'
                    },
                    columns: [
                        {
                            text        : 'Column 1',
                            dataIndex   : 'cl_one'
                        }, {
                            text        : 'Column 2',
                            dataIndex   : 'cl_two',
                            flex        : 1
                        }
                    ]
                }

Now, for the glue that binds everything together, take a look at the added function below that adds the panel to the parent tab panel on the grid click.

onGridItemClick  : function( me, record, item, index, e, eOpts ){
        var tabPanel = me.up('app-main').down('tabpanel'),
            data     = record.data;

        tabPanel.add({
            bind:{
                title       : 'ID ({some_title}) '
            },
            data    : data,
            closable: true,
            reference   : 'mypanel-' + data.id,
            xtype   : 'mycoolpanel'
        });
    }

The next function is taken directly from the view model controller and is called when the added event is fired. This is where the data is bound and the store load is called with the proper id.

	onFormAdd: function(me, parent){
       		 me.getViewModel().setData(me.config.data);
               }

Real Simple Extjs NVD3js Charts in place of Extjs Charts

Download the latest code from GitHub 

A little bit about the charts …

If you are into data visualization and developing in JavaScript then you have probably heard of d3js which can be found at d3js.org, you may not have heard about NVD3 which is a nice open source group of reusable charts based on d3js created by developers for the financial industry. You can find NVD3 here http://nvd3.org/ . What these two chart packages do is allow you, the developer, to control every last pixel on the screen. You are also left with code that can be moved away from Extjs if that day comes. Just take a look at the examples page to realize how many possibilities there are when it comes to charting: https://github.com/mbostock/d3/wiki/Gallery. I needed to get that type of flexibility in my application and I liked the crisp sharp lines so I went ahead and threw together some NVD3 / D3 based widgets that you can use in your applications by simply adding them and referencing the xtype. I am assuming that you will be using extjs 5 so the instructions here are for installation in an extjs5 application – that said they should work with extjs4 as well.

I will keep adding charts and updating functionality over the next few months and hope to have a comprehensive set of plugins that you can use.

Getting Started:

Add d3 and nv.d3 to your build.

  • First download d3.min.js and nv.d3.min.js.
  • Create a folder named js under your resources folder
  • Place the files in the new js folder
  • Change the lines in your app.json file below to include the new js files into your build
    "js": [
        {
            "path": "app.js",
            "bundle": true
        },{
            "path": "resources/js/d3.min.js",
            "bundle": false
        },{
            "path": "resources/js/nv.d3.min.js",
            "bundle": false
        }
    ],

Add nv.d3.min.css to your application

  • Download the nvd3 css file
  • Create a folder named css under your resources folder
  • Place the file in the new css folder
  • Change the lines in your app.json file below to include the new css files into your build
    "css": [
        {
            "path": "bootstrap.css",
            "bootstrap": true
        },{
            "path": "resources/css/nv.d3.css",
            "bootstrap": false
        }
    ],

Create a widget folder at the same level as the app folder.

D3Donutpanel

Screen Shot 2014-09-18 at 9.28.09 PM

Config options

  • dataUrl                    : (string) – url of json data for the chart, json should be in the following format, label and value are required, any attached data points will get passed to the click event i.e. primary keys etc ..
    {'results': 1, 'data':[{'some_value': 'not required', 'label': 'Required', 'value': 17}]}
  • chartTitle              : (string) – chart title displayed above the chart
  • tooltipAppend   : (string) – text to append to the tooltip value
  • showTotal            : (boolean) – show total in the center of the chart
  • totalType              : ‘sum’ or ‘avg’ calculate the total values as the sum of all values that are shown or the average of all shown values.