← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 15863: PT download csv with ou hierarchy + DV layout window + PT DV GIS ER EV ou tree synced with web api.

 

Merge authors:
  Jan Henrik Øverland (janhenrik-overland)
------------------------------------------------------------
revno: 15863 [merge]
committer: Jan Henrik Overland <janhenrik.overland@xxxxxxxxx>
branch nick: dhis2
timestamp: Thu 2014-06-26 16:49:55 +0200
message:
  PT download csv with ou hierarchy + DV layout window + PT DV GIS ER EV ou tree synced with web api.
modified:
  dhis-2/dhis-web/dhis-web-event-reports/src/main/webapp/dhis-web-event-reports/app/scripts/app.js
  dhis-2/dhis-web/dhis-web-event-visualizer/src/main/webapp/dhis-web-event-visualizer/app/scripts/app.js
  dhis-2/dhis-web/dhis-web-mapping/src/main/webapp/dhis-web-mapping/app/scripts/app.js
  dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/app.js
  dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/core.js
  dhis-2/dhis-web/dhis-web-visualizer/src/main/resources/org/hisp/dhis/visualizer/i18n_module.properties
  dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/i18n.json
  dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/app.js
  dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/core.js
  dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/plugin.js


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-web/dhis-web-event-reports/src/main/webapp/dhis-web-event-reports/app/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-event-reports/src/main/webapp/dhis-web-event-reports/app/scripts/app.js	2014-06-24 09:13:49 +0000
+++ dhis-2/dhis-web/dhis-web-event-reports/src/main/webapp/dhis-web-event-reports/app/scripts/app.js	2014-06-26 14:37:15 +0000
@@ -4537,13 +4537,13 @@
 				}
 			},
 			store: Ext.create('Ext.data.TreeStore', {
-				fields: ['id', 'name'],
+				fields: ['id', 'name', 'hasChildren'],
 				proxy: {
 					type: 'rest',
 					format: 'json',
 					noCache: false,
 					extraParams: {
-						fields: 'children[id,name,level]'
+						fields: 'children[id,name,children::isNotEmpty|rename(hasChildren)&paging=false'
 					},
 					url: ns.core.init.contextPath + '/api/organisationUnits',
 					reader: {
@@ -4563,15 +4563,9 @@
 				},
 				listeners: {
 					load: function(store, node, records) {
-                        var numberOfLevels = ns.core.init.organisationUnitLevels.length;
-
 						Ext.Array.each(records, function(record) {
-                            //if (Ext.isBoolean(record.data.hasChildren)) {
-                                //record.set('leaf', !record.data.hasChildren);
-                            //}
-
-                            if (Ext.isNumber(numberOfLevels)) {
-                                record.set('leaf', parseInt(record.raw.level) === numberOfLevels);
+                            if (Ext.isBoolean(record.data.hasChildren)) {
+                                record.set('leaf', !record.data.hasChildren);
                             }
                         });
 					}

=== modified file 'dhis-2/dhis-web/dhis-web-event-visualizer/src/main/webapp/dhis-web-event-visualizer/app/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-event-visualizer/src/main/webapp/dhis-web-event-visualizer/app/scripts/app.js	2014-06-23 11:47:18 +0000
+++ dhis-2/dhis-web/dhis-web-event-visualizer/src/main/webapp/dhis-web-event-visualizer/app/scripts/app.js	2014-06-26 14:37:15 +0000
@@ -4191,13 +4191,13 @@
 				}
 			},
 			store: Ext.create('Ext.data.TreeStore', {
-				fields: ['id', 'name'],
+				fields: ['id', 'name', 'hasChildren'],
 				proxy: {
 					type: 'rest',
 					format: 'json',
 					noCache: false,
 					extraParams: {
-						fields: 'children[id,name,level]'
+						fields: 'children[id,name,children::isNotEmpty|rename(hasChildren)&paging=false'
 					},
 					url: ns.core.init.contextPath + '/api/organisationUnits',
 					reader: {
@@ -4217,15 +4217,9 @@
 				},
 				listeners: {
 					load: function(store, node, records) {
-                        var numberOfLevels = ns.core.init.organisationUnitLevels.length;
-
 						Ext.Array.each(records, function(record) {
-                            //if (Ext.isBoolean(record.data.hasChildren)) {
-                                //record.set('leaf', !record.data.hasChildren);
-                            //}
-
-                            if (Ext.isNumber(numberOfLevels)) {
-                                record.set('leaf', parseInt(record.raw.level) === numberOfLevels);
+                            if (Ext.isBoolean(record.data.hasChildren)) {
+                                record.set('leaf', !record.data.hasChildren);
                             }
                         });
 					}

=== modified file 'dhis-2/dhis-web/dhis-web-mapping/src/main/webapp/dhis-web-mapping/app/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-mapping/src/main/webapp/dhis-web-mapping/app/scripts/app.js	2014-06-20 14:58:16 +0000
+++ dhis-2/dhis-web/dhis-web-mapping/src/main/webapp/dhis-web-mapping/app/scripts/app.js	2014-06-26 14:37:15 +0000
@@ -4682,13 +4682,13 @@
 				}
 			},
             store: Ext.create('Ext.data.TreeStore', {
-				fields: ['id', 'name'],
+				fields: ['id', 'name', 'hasChildren'],
 				proxy: {
 					type: 'rest',
 					format: 'json',
 					noCache: false,
 					extraParams: {
-						fields: 'children[id,name,level]'
+						fields: 'children[id,name,children::isNotEmpty|rename(hasChildren)&paging=false'
 					},
 					url: gis.init.contextPath + '/api/organisationUnits',
 					reader: {
@@ -4708,15 +4708,9 @@
 				},
 				listeners: {
 					load: function(store, node, records) {
-                        var numberOfLevels = gis.init.organisationUnitLevels.length;
-
 						Ext.Array.each(records, function(record) {
-                            //if (Ext.isBoolean(record.data.hasChildren)) {
-                                //record.set('leaf', !record.data.hasChildren);
-                            //}
-
-                            if (Ext.isNumber(numberOfLevels)) {
-                                record.set('leaf', parseInt(record.raw.level) === numberOfLevels);
+                            if (Ext.isBoolean(record.data.hasChildren)) {
+                                record.set('leaf', !record.data.hasChildren);
                             }
                         });
 					}
@@ -5387,13 +5381,13 @@
 				}
 			},
             store: Ext.create('Ext.data.TreeStore', {
-				fields: ['id', 'name'],
+				fields: ['id', 'name', 'hasChildren'],
 				proxy: {
 					type: 'rest',
 					format: 'json',
 					noCache: false,
 					extraParams: {
-						fields: 'children[id,name,level]'
+						fields: 'children[id,name,children::isNotEmpty|rename(hasChildren)&paging=false'
 					},
 					url: gis.init.contextPath + '/api/organisationUnits',
 					reader: {
@@ -5413,15 +5407,9 @@
 				},
 				listeners: {
 					load: function(store, node, records) {
-                        var numberOfLevels = gis.init.organisationUnitLevels.length;
-
 						Ext.Array.each(records, function(record) {
-                            //if (Ext.isBoolean(record.data.hasChildren)) {
-                                //record.set('leaf', !record.data.hasChildren);
-                            //}
-
-                            if (Ext.isNumber(numberOfLevels)) {
-                                record.set('leaf', parseInt(record.raw.level) === numberOfLevels);
+                            if (Ext.isBoolean(record.data.hasChildren)) {
+                                record.set('leaf', !record.data.hasChildren);
                             }
                         });
 					}
@@ -6096,13 +6084,13 @@
 				}
 			},
             store: Ext.create('Ext.data.TreeStore', {
-				fields: ['id', 'name'],
+				fields: ['id', 'name', 'hasChildren'],
 				proxy: {
 					type: 'rest',
 					format: 'json',
 					noCache: false,
 					extraParams: {
-						fields: 'children[id,name,level]'
+						fields: 'children[id,name,children::isNotEmpty|rename(hasChildren)&paging=false'
 					},
 					url: gis.init.contextPath + '/api/organisationUnits',
 					reader: {
@@ -6122,15 +6110,9 @@
 				},
 				listeners: {
 					load: function(store, node, records) {
-                        var numberOfLevels = gis.init.organisationUnitLevels.length;
-
 						Ext.Array.each(records, function(record) {
-                            //if (Ext.isBoolean(record.data.hasChildren)) {
-                                //record.set('leaf', !record.data.hasChildren);
-                            //}
-
-                            if (Ext.isNumber(numberOfLevels)) {
-                                record.set('leaf', parseInt(record.raw.level) === numberOfLevels);
+                            if (Ext.isBoolean(record.data.hasChildren)) {
+                                record.set('leaf', !record.data.hasChildren);
                             }
                         });
 					}
@@ -7429,13 +7411,13 @@
 				}
 			},
             store: Ext.create('Ext.data.TreeStore', {
-				fields: ['id', 'name'],
+				fields: ['id', 'name', 'hasChildren'],
 				proxy: {
 					type: 'rest',
 					format: 'json',
 					noCache: false,
 					extraParams: {
-						fields: 'children[id,name,level]'
+						fields: 'children[id,name,children::isNotEmpty|rename(hasChildren)&paging=false'
 					},
 					url: gis.init.contextPath + '/api/organisationUnits',
 					reader: {
@@ -7455,15 +7437,9 @@
 				},
 				listeners: {
 					load: function(store, node, records) {
-                        var numberOfLevels = gis.init.organisationUnitLevels.length;
-
 						Ext.Array.each(records, function(record) {
-                            //if (Ext.isBoolean(record.data.hasChildren)) {
-                                //record.set('leaf', !record.data.hasChildren);
-                            //}
-
-                            if (Ext.isNumber(numberOfLevels)) {
-                                record.set('leaf', parseInt(record.raw.level) === numberOfLevels);
+                            if (Ext.isBoolean(record.data.hasChildren)) {
+                                record.set('leaf', !record.data.hasChildren);
                             }
                         });
 					}

=== modified file 'dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/app.js	2014-06-23 13:21:30 +0000
+++ dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/app.js	2014-06-26 14:37:15 +0000
@@ -2042,6 +2042,38 @@
 				}
 			};
 
+            // document
+            web.document = web.document || {};
+
+            web.document.printResponseCSV = function(response) {
+                var headers = response.headers,
+                    names = response.metaData.names,
+                    rows = response.rows,
+                    csv = '',
+                    alink;
+
+                // headers
+                for (var i = 0; i < headers.length; i++) {
+                    csv += headers[i].column + (i < headers.length - 1 ? ',' : '\n');
+                }
+
+                // rows
+                for (var i = 0; i < rows.length; i++) {
+                    for (var j = 0, id, isMeta; j < rows[i].length; j++) {
+                        val = rows[i][j];
+                        isMeta = headers[j].meta;
+
+                        csv += isMeta && names[val] ? names[val] : val;
+                        csv += j < rows[i].length - 1 ? ',' : '\n';
+                    }
+                }
+
+                alink = document.createElement('a');
+                alink.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv));
+                alink.setAttribute('download', 'data.csv');
+                alink.click();
+            };
+
 			// mouse events
 			web.events = web.events || {};
 
@@ -4399,13 +4431,13 @@
 				}
 			},
 			store: Ext.create('Ext.data.TreeStore', {
-				fields: ['id', 'name'],
+				fields: ['id', 'name', 'hasChildren'],
 				proxy: {
 					type: 'rest',
 					format: 'json',
 					noCache: false,
 					extraParams: {
-						fields: 'children[id,name,level]'
+						fields: 'children[id,name,children::isNotEmpty|rename(hasChildren)&paging=false'
 					},
 					url: ns.core.init.contextPath + '/api/organisationUnits',
 					reader: {
@@ -4425,15 +4457,9 @@
 				},
 				listeners: {
 					load: function(store, node, records) {
-                        var numberOfLevels = ns.core.init.organisationUnitLevels.length;
-
 						Ext.Array.each(records, function(record) {
-                            //if (Ext.isBoolean(record.data.hasChildren)) {
-                                //record.set('leaf', !record.data.hasChildren);
-                            //}
-
-                            if (Ext.isNumber(numberOfLevels)) {
-                                record.set('leaf', parseInt(record.raw.level) === numberOfLevels);
+                            if (Ext.isBoolean(record.data.hasChildren)) {
+                                record.set('leaf', !record.data.hasChildren);
                             }
                         });
 					}
@@ -4787,7 +4813,7 @@
 
             onSelect = function() {
                 var win = ns.app.layoutWindow;
-console.log(selectedStore.getRange().length, win.hasDimension(dimension.id));
+
                 if (selectedStore.getRange().length) {
                     win.addDimension({id: dimension.id, name: dimension.name});
                 }
@@ -5225,124 +5251,118 @@
 		downloadButton = Ext.create('Ext.button.Button', {
 			text: 'Download',
 			disabled: true,
-			menu: {
-				cls: 'ns-menu',
-				shadow: false,
-				showSeparator: false,
-				items: [
-					{
-						xtype: 'label',
-						text: NS.i18n.table_layout,
-						style: 'padding:7px 5px 5px 7px; font-weight:bold; border:0 none'
-					},
-					{
-						text: 'Microsoft Excel (.xls)',
-						iconCls: 'ns-menu-item-tablelayout',
-						handler: function() {
-							openTableLayoutTab('xls');
-						}
-					},
-					{
-						text: 'CSV (.csv)',
-						iconCls: 'ns-menu-item-tablelayout',
-						handler: function() {
-							openTableLayoutTab('csv');
-						}
-					},
-					{
-						text: 'HTML (.html)',
-						iconCls: 'ns-menu-item-tablelayout',
-						handler: function() {
-							openTableLayoutTab('html', true);
-						}
-					},
-					{
-						xtype: 'label',
-						text: NS.i18n.plain_data_sources,
-						style: 'padding:7px 5px 5px 7px; font-weight:bold'
-					},
-					{
-						text: 'JSON',
-						iconCls: 'ns-menu-item-datasource',
-						handler: function() {
-							if (ns.core.init.contextPath && ns.app.paramString) {
-								window.open(ns.core.init.contextPath + '/api/analytics.json' + getParamString(), '_blank');
-							}
-						}
-					},
-					{
-						text: 'XML',
-						iconCls: 'ns-menu-item-datasource',
-						handler: function() {
-							if (ns.core.init.contextPath && ns.app.paramString) {
-								window.open(ns.core.init.contextPath + '/api/analytics.xml' + getParamString(), '_blank');
-							}
-						}
-					},
-					{
-						text: 'Microsoft Excel',
-						iconCls: 'ns-menu-item-datasource',
-						handler: function() {
-							if (ns.core.init.contextPath && ns.app.paramString) {
-								window.location.href = ns.core.init.contextPath + '/api/analytics.xls' + getParamString();
-							}
-						}
-					},
-					{
-						text: 'CSV',
-						iconCls: 'ns-menu-item-datasource',
-						handler: function() {
-							if (ns.core.init.contextPath && ns.app.paramString) {
-								window.location.href = ns.core.init.contextPath + '/api/analytics.csv' + getParamString();
-							}
-						}
-					},
-					{
-						text: 'JRXML',
-						iconCls: 'ns-menu-item-datasource',
-						handler: function() {
-							if (ns.core.init.contextPath && ns.app.paramString) {
-								window.open(ns.core.init.contextPath + '/api/analytics.jrxml' + getParamString(), '_blank');
-							}
-						}
-					}
-                    //{
-                        //text: 'export',
-                        //handler: function() {
-                            //var myWin = window.open(),
-                                //tableId = 'datatable',
-                                //text = '';
-
-                            ////text += '<a id="csvlink" href="" download="datatable.csv" onclick="javascript:this.download()">Download CSV</a><br/><br/>';
-                            //text += '<a href="" download="data.csv">download</a>';
-
-                            //text += '<table id="datatable">';
-
-                            //text += '<tr><th>indicator</th><th>period</th><th>orgunit</th><th>value</th></tr>';
-
-                            //text += '<tr><td>anc1</td><td>jan</td><td>telemark</td><td>8</td></tr>';
-                            //text += '<tr><td>anc1</td><td>jan</td><td>oslo</td><td>2</td></tr>';
-                            //text += '<tr><td>anc1</td><td>feb</td><td>telemark</td><td>11</td></tr>';
-                            //text += '<tr><td>anc1</td><td>feb</td><td>oslo</td><td>12</td></tr>';
-
-                            //text += '</table>';
-
-                            //myWin.document.write(text);
-
-                            //myWin.document.getElementById('csvlink').download = function() {
-                                //return ExcellentExport.csv(window, 'datatable');
-                            //};
-                        //}
-                    //}
-				],
-				listeners: {
-					added: function() {
-						ns.app.downloadButton = this;
-					},
-					afterrender: function() {
-						this.getEl().addCls('ns-toolbar-btn-menu');
-					}
-				}
+			menu: {},
+            handler: function(b) {
+                b.menu = Ext.create('Ext.menu.Menu', {
+                    closeAction: 'destroy',
+                    //cls: 'ns-menu',
+                    shadow: false,
+                    showSeparator: false,
+                    items: [
+                        {
+                            xtype: 'label',
+                            text: NS.i18n.table_layout,
+                            style: 'padding:7px 5px 5px 7px; font-weight:bold; border:0 none'
+                        },
+                        {
+                            text: 'Microsoft Excel (.xls)',
+                            iconCls: 'ns-menu-item-tablelayout',
+                            handler: function() {
+                                openTableLayoutTab('xls');
+                            }
+                        },
+                        {
+                            text: 'CSV (.csv)',
+                            iconCls: 'ns-menu-item-tablelayout',
+                            handler: function() {
+                                openTableLayoutTab('csv');
+                            }
+                        },
+                        {
+                            text: 'HTML (.html)',
+                            iconCls: 'ns-menu-item-tablelayout',
+                            handler: function() {
+                                openTableLayoutTab('html', true);
+                            }
+                        },
+                        {
+                            xtype: 'label',
+                            text: NS.i18n.plain_data_sources,
+                            style: 'padding:7px 5px 5px 7px; font-weight:bold'
+                        },
+                        {
+                            text: 'JSON',
+                            iconCls: 'ns-menu-item-datasource',
+                            handler: function() {
+                                if (ns.core.init.contextPath && ns.app.paramString) {
+                                    window.open(ns.core.init.contextPath + '/api/analytics.json' + getParamString(), '_blank');
+                                }
+                            }
+                        },
+                        {
+                            text: 'XML',
+                            iconCls: 'ns-menu-item-datasource',
+                            handler: function() {
+                                if (ns.core.init.contextPath && ns.app.paramString) {
+                                    window.open(ns.core.init.contextPath + '/api/analytics.xml' + getParamString(), '_blank');
+                                }
+                            }
+                        },
+                        {
+                            text: 'Microsoft Excel',
+                            iconCls: 'ns-menu-item-datasource',
+                            handler: function() {
+                                if (ns.core.init.contextPath && ns.app.paramString) {
+                                    window.location.href = ns.core.init.contextPath + '/api/analytics.xls' + getParamString();
+                                }
+                            }
+                        },
+                        {
+                            text: 'CSV',
+                            iconCls: 'ns-menu-item-datasource',
+                            handler: function() {
+                                if (ns.core.init.contextPath && ns.app.paramString) {
+                                    window.location.href = ns.core.init.contextPath + '/api/analytics.csv' + getParamString();
+                                }
+                            }
+                        },
+                        {
+                            text: 'CSV w/ hierarchy',
+                            iconCls: 'ns-menu-item-datasource',
+                            hidden: !(ns.app.layout && !!ns.app.layout.showHierarchy && ns.app.xResponse.nameHeaderMap.hasOwnProperty('ou')),
+                            handler: function() {
+                                var response = ns.core.service.response.addOuHierarchyDimensions(Ext.clone(ns.app.response));
+
+                                ns.core.web.document.printResponseCSV(response);
+                            }
+                        },
+                        {
+                            text: 'JRXML',
+                            iconCls: 'ns-menu-item-datasource',
+                            handler: function() {
+                                if (ns.core.init.contextPath && ns.app.paramString) {
+                                    window.open(ns.core.init.contextPath + '/api/analytics.jrxml' + getParamString(), '_blank');
+                                }
+                            }
+                        }
+                    ],
+                    listeners: {
+                        added: function() {
+                            ns.app.downloadButton = this;
+                        },
+                        show: function() {
+                            ns.core.web.window.setAnchorPosition(b.menu, b);
+                        },
+                        hide: function() {
+                            b.menu.destroy();
+                        },
+                        destroy: function(m) {
+                            b.menu = null;
+                        }
+                    }
+                });
+
+                this.menu.show();
 			}
 		});
 

=== modified file 'dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/core.js'
--- dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/core.js	2014-06-18 11:06:05 +0000
+++ dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/core.js	2014-06-25 14:03:47 +0000
@@ -1076,7 +1076,6 @@
 
 			service.layout.getSyncronizedXLayout = function(xLayout, response) {
 				var removeDimensionFromXLayout,
-                    addOuHierarchyDimensions,
 					dimensions = Ext.Array.clean([].concat(xLayout.columns || [], xLayout.rows || [], xLayout.filters || [])),
                     xOuDimension = xLayout.objectNameDimensionsMap[dimConf.organisationUnit.objectName],
                     isUserOrgunit = xOuDimension && Ext.Array.contains(xOuDimension.ids, 'USER_ORGUNIT'),
@@ -1148,94 +1147,6 @@
 					}
 				};
 
-                addOuHierarchyDimensions = function() {
-                    var axis = xLayout.dimensionNameAxisMap[ou],
-                        ouHierarchy = response.metaData.ouHierarchy,
-                        idsByLevel = [],
-                        objectsByLevel = [],
-                        ouIndex,
-                        numLevels = 0,
-                        a;
-
-                    // get ou index
-                    for (var i = 0; i < axis.length; i++) {
-                        if (axis[i].dimensionName === ou) {
-                            ouIndex = i;
-                            break;
-                        }
-                    }
-
-                    // number of levels
-                    for (var key in ouHierarchy) {
-                        if (ouHierarchy.hasOwnProperty(key)) {
-                            a = Ext.Array.clean(ouHierarchy[key].split('/'));
-
-                            numLevels = Math.max(a.length, numLevels);
-                        }
-                    }
-
-                    numLevels = numLevels + 1;
-
-                    // create level arrays
-                    for (var i = 0; i < numLevels; i++) {
-                        idsByLevel.push([]);
-                        objectsByLevel.push([]);
-                    }
-
-                    // populate levels
-                    for (var key in ouHierarchy) {
-                        if (ouHierarchy.hasOwnProperty(key)) {
-                            a = Ext.Array.clean(ouHierarchy[key].split('/'));
-                            a.push(key);
-
-                            if (a.length === numLevels) {
-                                for (var i = 0, id; i < a.length; i++) {
-                                    id = a[i];
-
-                                    idsByLevel[i].push(id);
-                                }
-                            }
-                        }
-                    }
-
-                    // unique only, create objects
-                    for (var i = 0, idLevel; i < idsByLevel.length; i++) {
-                        idLevel = idsByLevel[i];
-                        idLevel = Ext.Array.unique(idLevel);
-
-                        for (var j = 0; j < idLevel.length; j++) {
-                            objectsByLevel[i].push({
-                                id: idLevel[j],
-                                name: response.metaData.names[idLevel[j]]
-                            });
-                        }
-                    }
-
-                    objectsByLevel = Ext.Array.clean(objectsByLevel);
-                    objectsByLevel.shift();
-
-                    // remove ou dimension
-                    axis = Ext.Array.erase(axis, ouIndex, 1);
-
-                    // insert levels
-                    for (var i = 0; i < objectsByLevel.length; i++) {
-                        Ext.Array.insert(axis, ouIndex, [{
-                            dimension: 'ou' + (i + 1),
-                            dimensionName: 'ou' + (i + 1),
-                            objectName: 'ou' + (i + 1),
-                            ids: idsByLevel[i],
-                            items: objectsByLevel[i]
-                        }]);
-
-                        ouIndex = ouIndex + 1;
-                    }
-
-                console.log("axis", axis);
-                console.log("objectsByLevel", objectsByLevel);
-
-
-                };
-
                 // Set items from init/metaData/xLayout
                 for (var i = 0, dim, metaDataDim, items; i < dimensions.length; i++) {
                     dim = dimensions[i];
@@ -1852,7 +1763,63 @@
 
 				return response;
 			};
-		}());
+
+            service.response.addOuHierarchyDimensions = function(response) {
+                var headers = response.headers,
+                    ouHierarchy = response.metaData.ouHierarchy,
+                    rows = response.rows,
+                    ouIndex,
+                    numLevels = 0,
+                    initArray = [],
+                    newHeaders = [],
+                    a;
+
+                if (!ouHierarchy) {
+                    return;
+                }
+
+                // get ou index
+                for (var i = 0; i < headers.length; i++) {
+                    if (headers[i].name === 'ou') {
+                        ouIndex = i;
+                        break;
+                    }
+                }
+
+                // get numLevels
+                for (var i = 0; i < rows.length; i++) {
+                    numLevels = Math.max(numLevels, Ext.Array.clean(ouHierarchy[rows[i][ouIndex]].split('/')).length);
+                }
+
+                // init array
+                for (var i = 0; i < numLevels; i++) {
+                    initArray.push('');
+                }
+
+                // extend rows
+                for (var i = 0, row, ouArray; i < rows.length; i++) {
+                    row = rows[i];
+                    ouArray = Ext.applyIf(Ext.Array.clean(ouHierarchy[row[ouIndex]].split('/')), Ext.clone(initArray));
+
+                    Ext.Array.insert(row, ouIndex, ouArray);
+                }
+
+                // create new headers
+                for (var i = 0; i < numLevels; i++) {
+                    newHeaders.push({
+                        column: 'Organisation unit',
+                        hidden: false,
+                        meta: true,
+                        name: 'ou',
+                        type: 'java.lang.String'
+                    });
+                }
+
+                Ext.Array.insert(headers, ouIndex, newHeaders);
+
+                return response;
+            };
+        }());
 
 		// web
 		(function() {

=== modified file 'dhis-2/dhis-web/dhis-web-visualizer/src/main/resources/org/hisp/dhis/visualizer/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-visualizer/src/main/resources/org/hisp/dhis/visualizer/i18n_module.properties	2014-05-13 22:16:51 +0000
+++ dhis-2/dhis-web/dhis-web-visualizer/src/main/resources/org/hisp/dhis/visualizer/i18n_module.properties	2014-06-26 14:37:15 +0000
@@ -223,4 +223,5 @@
 open_last_pivot_table=Open last pivot table
 go_to_maps=Go to maps
 open_this_chart_as_map=Open this chart as map
-open_last_map=Open last map
\ No newline at end of file
+open_last_map=Open last map
+dimensions=Dimensions
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/i18n.json'
--- dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/i18n.json	2014-05-13 22:16:51 +0000
+++ dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/i18n.json	2014-06-26 14:37:15 +0000
@@ -223,5 +223,6 @@
 "open_last_pivot_table",
 "go_to_maps",
 "open_this_chart_as_map",
-"open_last_map"
+"open_last_map",
+"dimensions"
 ]

=== modified file 'dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/app.js	2014-06-20 10:41:50 +0000
+++ dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/app.js	2014-06-26 14:37:15 +0000
@@ -1,6 +1,7 @@
 Ext.onReady( function() {
 	var NS = DV,
 
+    LayoutWindow,
 	OptionsWindow,
 	FavoriteWindow,
 	SharingWindow,
@@ -34,6 +35,425 @@
 	}());
 
 	// constructors
+	LayoutWindow = function() {
+		var dimension,
+			dimensionStore,
+			row,
+			rowStore,
+			col,
+			colStore,
+			filter,
+			filterStore,
+			value,
+
+			getStore,
+			getStoreKeys,
+            addDimension,
+            removeDimension,
+            hasDimension,
+            saveState,
+            resetData,
+            reset,
+            dimensionStoreMap = {},
+
+			dimensionPanel,
+			selectPanel,
+			window,
+
+			margin = 1,
+			defaultWidth = 160,
+			defaultHeight = 200;
+
+		getStore = function(data) {
+			var config = {};
+
+			config.fields = ['id', 'name'];
+
+			if (data) {
+				config.data = data;
+			}
+
+			config.getDimensionNames = function() {
+				var dimensionNames = [];
+
+				this.each(function(r) {
+					dimensionNames.push(r.data.id);
+				});
+
+				return Ext.clone(dimensionNames);
+			};
+
+			return Ext.create('Ext.data.Store', config);
+		};
+
+		getStoreKeys = function(store) {
+			var keys = [],
+				items = store.data.items;
+
+			if (items) {
+				for (var i = 0; i < items.length; i++) {
+					keys.push(items[i].data.id);
+				}
+			}
+
+			return keys;
+		};
+
+		dimensionStore = getStore();
+        ns.app.stores.dimension = dimensionStore;
+
+		colStore = getStore();
+        ns.app.stores.col = colStore;
+
+		rowStore = getStore();
+        ns.app.stores.row = rowStore;
+
+        filterStore = getStore();
+        ns.app.stores.filter = filterStore;
+
+		dimension = Ext.create('Ext.ux.form.MultiSelect', {
+			cls: 'ns-toolbar-multiselect-leftright',
+			width: defaultWidth,
+			height: (defaultHeight * 2) + margin,
+			style: 'margin-right:' + margin + 'px; margin-bottom:0px',
+			valueField: 'id',
+			displayField: 'name',
+			dragGroup: 'layoutDD',
+			dropGroup: 'layoutDD',
+			ddReorder: false,
+			store: dimensionStore,
+			tbar: {
+				height: 25,
+				items: {
+					xtype: 'label',
+					text: NS.i18n.dimensions,
+					cls: 'ns-toolbar-multiselect-leftright-label'
+				}
+			},
+			listeners: {
+				afterrender: function(ms) {
+					ms.store.on('add', function() {
+						Ext.defer( function() {
+							ms.boundList.getSelectionModel().deselectAll();
+						}, 10);
+					});
+				}
+			}
+		});
+
+		col = Ext.create('Ext.ux.form.MultiSelect', {
+			cls: 'ns-toolbar-multiselect-leftright',
+			width: defaultWidth,
+			height: defaultHeight,
+			style: 'margin-bottom:' + margin + 'px',
+			valueField: 'id',
+			displayField: 'name',
+			dragGroup: 'layoutDD',
+			dropGroup: 'layoutDD',
+			store: colStore,
+			tbar: {
+				height: 25,
+				items: {
+					xtype: 'label',
+					text: NS.i18n.series,
+					cls: 'ns-toolbar-multiselect-leftright-label'
+				}
+			},
+			listeners: {
+				afterrender: function(ms) {
+					ms.boundList.on('itemdblclick', function(view, record) {
+						ms.store.remove(record);
+						dimensionStore.add(record);
+					});
+
+					ms.store.on('add', function(store, addedRecords) {
+                        var range = store.getRange();
+                        
+                        if (range.length > 1) {
+                            var addedIds = Ext.Array.pluck(addedRecords, 'internalId'),
+                                records = Ext.clone(range);
+
+                            store.removeAll();
+
+                            for (var i = 0; i < range.length; i++) {
+                                if (Ext.Array.contains(addedIds, range[i].internalId)) {
+                                    store.add(range[i]);
+                                }
+                                else {
+                                    filterStore.add(range[i]);
+                                }
+                            }
+                        }
+                        
+						Ext.defer( function() {
+							ms.boundList.getSelectionModel().deselectAll();
+						}, 10);                        
+					});
+				}
+			}
+		});
+
+		row = Ext.create('Ext.ux.form.MultiSelect', {
+			cls: 'ns-toolbar-multiselect-leftright',
+			width: defaultWidth,
+			height: defaultHeight,
+			style: 'margin-bottom:0px',
+			valueField: 'id',
+			displayField: 'name',
+			dragGroup: 'layoutDD',
+			dropGroup: 'layoutDD',
+			store: rowStore,
+			tbar: {
+				height: 25,
+				items: {
+					xtype: 'label',
+					text: NS.i18n.category,
+					cls: 'ns-toolbar-multiselect-leftright-label'
+				}
+			},
+			listeners: {
+				afterrender: function(ms) {
+					ms.boundList.on('itemdblclick', function(view, record) {
+						ms.store.remove(record);
+						dimensionStore.add(record);
+					});
+
+					ms.store.on('add', function(store, addedRecords) {
+                        var range = store.getRange();
+                        
+                        if (range.length > 1) {
+                            var addedIds = Ext.Array.pluck(addedRecords, 'internalId'),
+                                records = Ext.clone(range);
+
+                            store.removeAll();
+
+                            for (var i = 0; i < range.length; i++) {
+                                if (Ext.Array.contains(addedIds, range[i].internalId)) {
+                                    store.add(range[i]);
+                                }
+                                else {
+                                    filterStore.add(range[i]);
+                                }
+                            }
+                        }
+                        
+						Ext.defer( function() {
+							ms.boundList.getSelectionModel().deselectAll();
+						}, 10);                        
+					});
+				}
+			}
+		});
+
+		filter = Ext.create('Ext.ux.form.MultiSelect', {
+			cls: 'ns-toolbar-multiselect-leftright',
+			width: defaultWidth,
+			height: defaultHeight,
+			style: 'margin-right:' + margin + 'px; margin-bottom:' + margin + 'px',
+			valueField: 'id',
+			displayField: 'name',
+			dragGroup: 'layoutDD',
+			dropGroup: 'layoutDD',
+			store: filterStore,
+			tbar: {
+				height: 25,
+				items: {
+					xtype: 'label',
+					text: NS.i18n.filter,
+					cls: 'ns-toolbar-multiselect-leftright-label'
+				}
+			},
+			listeners: {
+				afterrender: function(ms) {
+					ms.boundList.on('itemdblclick', function(view, record) {
+						ms.store.remove(record);
+						dimensionStore.add(record);
+					});
+
+					ms.store.on('add', function() {
+						Ext.defer( function() {
+							ms.boundList.getSelectionModel().deselectAll();
+						}, 10);
+					});
+				}
+			}
+		});
+
+		selectPanel = Ext.create('Ext.panel.Panel', {
+			bodyStyle: 'border:0 none',
+			items: [
+				{
+					layout: 'column',
+					bodyStyle: 'border:0 none',
+					items: [
+						filter,
+						col
+					]
+				},
+				{
+					layout: 'column',
+					bodyStyle: 'border:0 none',
+					items: [
+						row
+					]
+				}
+			]
+		});
+
+        addDimension = function(record, store) {
+            var store = dimensionStoreMap[record.id] || store || colStore;
+
+            if (!hasDimension(record.id)) {
+                store.add(record);
+            }
+        };
+
+        removeDimension = function(dataElementId) {
+            var stores = [colStore, rowStore, filterStore];
+
+            for (var i = 0, store, index; i < stores.length; i++) {
+                store = stores[i];
+                index = store.findExact('id', dataElementId);
+
+                if (index != -1) {
+                    store.remove(store.getAt(index));
+                    dimensionStoreMap[dataElementId] = store;
+                }
+            }
+        };
+
+        hasDimension = function(id) {
+            var stores = [colStore, rowStore, filterStore];
+
+            for (var i = 0, store, index; i < stores.length; i++) {
+                store = stores[i];
+                index = store.findExact('id', id);
+
+                if (index != -1) {
+                    return true;
+                }
+            }
+
+            return false;
+        };
+
+        saveState = function(map) {
+			map = map || dimensionStoreMap;
+
+            colStore.each(function(record) {
+                map[record.data.id] = colStore;
+            });
+
+            rowStore.each(function(record) {
+                map[record.data.id] = rowStore;
+            });
+
+            filterStore.each(function(record) {
+                map[record.data.id] = filterStore;
+            });
+
+            return map;
+        };
+
+		resetData = function() {
+			var map = saveState({}),
+				keys = ['dx', 'ou', 'pe', 'dates'];
+
+			for (var key in map) {
+				if (map.hasOwnProperty(key) && !Ext.Array.contains(keys, key)) {
+					removeDimension(key);
+				}
+			}
+		};
+
+		reset = function(isAll) {
+			colStore.removeAll();
+			rowStore.removeAll();
+			filterStore.removeAll();
+
+			if (!isAll) {
+				colStore.add({id: dimConf.data.dimensionName, name: dimConf.data.name});
+				rowStore.add({id: dimConf.period.dimensionName, name: dimConf.period.name});
+				filterStore.add({id: dimConf.organisationUnit.dimensionName, name: dimConf.organisationUnit.name});
+			}
+		};
+
+		getSetup = function() {
+			return {
+				col: getStoreKeys(colStore),
+				row: getStoreKeys(rowStore),
+				filter: getStoreKeys(filterStore)
+			};
+		};
+
+		window = Ext.create('Ext.window.Window', {
+			title: NS.i18n.table_layout,
+			bodyStyle: 'background-color:#fff; padding:' + margin + 'px',
+			closeAction: 'hide',
+			autoShow: true,
+			modal: true,
+			resizable: false,
+			getSetup: getSetup,
+			dimensionStore: dimensionStore,
+			rowStore: rowStore,
+			colStore: colStore,
+			filterStore: filterStore,
+            addDimension: addDimension,
+            removeDimension: removeDimension,
+            hasDimension: hasDimension,
+			hideOnBlur: true,
+			items: {
+				layout: 'column',
+				bodyStyle: 'border:0 none',
+				items: [
+					dimension,
+					selectPanel
+				]
+			},
+			bbar: [
+				'->',
+				{
+					text: NS.i18n.hide,
+					listeners: {
+						added: function(b) {
+							b.on('click', function() {
+								window.hide();
+							});
+						}
+					}
+				},
+				{
+					text: '<b>' + NS.i18n.update + '</b>',
+					listeners: {
+						added: function(b) {
+							b.on('click', function() {
+                                ns.app.viewport.update();
+
+								window.hide();
+							});
+						}
+					}
+				}
+			],
+			listeners: {
+				show: function(w) {
+					if (ns.app.layoutButton.rendered) {
+						ns.core.web.window.setAnchorPosition(w, ns.app.layoutButton);
+
+						if (!w.hasHideOnBlurHandler) {
+							ns.core.web.window.addHideOnBlurHandler(w);
+						}
+					}
+				},
+                render: function() {
+					reset();
+                }
+			}
+		});
+
+		return window;
+	};
+
 	OptionsWindow = function() {
 		var showValues,
             hideEmptyRows,
@@ -436,14 +856,7 @@
 				{
 					text: '<b>' + NS.i18n.update + '</b>',
 					handler: function() {
-						var config = ns.core.web.chart.getLayoutConfig(),
-							layout = ns.core.api.layout.Layout(config);
-
-						if (!layout) {
-							return;
-						}
-
-						ns.core.web.chart.getData(layout, false);
+                        ns.app.viewport.update();
 
 						window.hide();
 					}
@@ -1713,9 +2126,9 @@
 
 			web.chart.getLayoutConfig = function() {
 				var panels = ns.app.accordion.panels,
-                    columnDimNames = [ns.app.viewport.series.getValue()],
-                    rowDimNames = [ns.app.viewport.category.getValue()],
-                    filterDimNames = ns.app.viewport.filter.getValue(),
+					columnDimNames = [ns.app.stores.col.getDimensionNames()],
+					rowDimNames = [ns.app.stores.row.getDimensionNames()],
+					filterDimNames = [ns.app.stores.filter.getDimensionNames()],
 					config = ns.app.optionsWindow.getOptions(),
 					dx = dimConf.data.dimensionName,
 					co = dimConf.category.dimensionName,
@@ -1982,6 +2395,7 @@
 			accordionBody,
             accordion,
             westRegion,
+            layoutButton,
             optionsButton,
             favoriteButton,
             getParamString,
@@ -2094,7 +2508,7 @@
 
         chartType = Ext.create('Ext.toolbar.Toolbar', {
             height: 45,
-            style: 'padding-top:0px; border-style:none',
+            style: 'padding-top:1px; border:0 none; border-bottom:1px solid #ddd',
             getChartType: function() {
                 for (var i = 0; i < buttons.length; i++) {
                     if (buttons[i].pressed) {
@@ -2161,167 +2575,6 @@
 			});
 		};
 
-        colStore = getDimensionStore();
-		ns.app.stores.col = colStore;
-
-        rowStore = getDimensionStore();
-		ns.app.stores.row = rowStore;
-
-        filterStore = getDimensionStore();
-		ns.app.stores.filter = filterStore;
-
-        series = Ext.create('Ext.form.field.ComboBox', {
-            cls: 'ns-combo',
-            baseBodyCls: 'small',
-            style: 'margin-bottom:0',
-            name: ns.core.conf.finals.chart.series,
-            queryMode: 'local',
-            editable: false,
-            valueField: 'id',
-            displayField: 'name',
-            width: (ns.core.conf.layout.west_fieldset_width / 3),
-            value: ns.core.conf.finals.dimension.data.dimensionName,
-            filterNext: function() {
-                category.filter(this.getValue());
-                filter.filter([this.getValue(), category.getValue()]);
-            },
-            store: colStore,
-            listeners: {
-                added: function(cb) {
-                    cb.filterNext();
-                },
-                select: function(cb) {
-                    cb.filterNext();
-                }
-            }
-        });
-
-        category = Ext.create('Ext.form.field.ComboBox', {
-            cls: 'ns-combo',
-            baseBodyCls: 'small',
-            style: 'margin-bottom:0',
-            name: ns.core.conf.finals.chart.category,
-            queryMode: 'local',
-            editable: false,
-            lastQuery: '',
-            valueField: 'id',
-            displayField: 'name',
-            width: (ns.core.conf.layout.west_fieldset_width / 3),
-            value: ns.core.conf.finals.dimension.period.dimensionName,
-            filter: function(value) {
-                if (Ext.isString(value)) {
-                    if (value === this.getValue()) {
-                        this.clearValue();
-                    }
-
-                    this.store.clearFilter();
-
-                    this.store.filterBy(function(record, id) {
-                        return id !== value;
-                    });
-                }
-            },
-            filterNext: function() {
-                filter.filter([series.getValue(), this.getValue()]);
-            },
-            store: rowStore,
-            listeners: {
-                added: function(cb) {
-                    cb.filterNext();
-                },
-                select: function(cb) {
-                    cb.filterNext();
-                }
-            }
-        });
-
-        filter = Ext.create('Ext.form.field.ComboBox', {
-            cls: 'ns-combo',
-            multiSelect: true,
-            baseBodyCls: 'small',
-            style: 'margin-bottom:0',
-            name: ns.core.conf.finals.chart.filter,
-            queryMode: 'local',
-            editable: false,
-            lastQuery: '',
-            valueField: 'id',
-            displayField: 'name',
-            width: (ns.core.conf.layout.west_fieldset_width / 3) + 1,
-            value: ns.core.conf.finals.dimension.organisationUnit.dimensionName,
-            filter: function(values) {
-                var a = Ext.clone(this.getValue()),
-                    b = [];
-
-                for (var i = 0; i < a.length; i++) {
-                    if (!Ext.Array.contains(values, a[i])) {
-                        b.push(a[i]);
-                    }
-                }
-
-                this.clearValue();
-                this.setValue(b);
-
-                this.store.filterBy(function(record, id) {
-                    return !Ext.Array.contains(values, id);
-                });
-            },
-            store: filterStore,
-            listeners: {
-                beforedeselect: function(cb) {
-                    return cb.getValue().length !== 1;
-                }
-            }
-        });
-
-        layout = Ext.create('Ext.toolbar.Toolbar', {
-            id: 'chartlayout_tb',
-            style: 'padding:2px 0 0 1px; background:#f5f5f5; border:0 none; border-top:1px dashed #ccc; border-bottom:1px solid #ccc',
-            height: 45,
-            items: [
-                {
-                    xtype: 'container',
-                    bodyStyle: 'border-style:none; background-color:transparent; padding:0',
-                    style: 'margin:0',
-                    items: [
-                        {
-                            xtype: 'label',
-                            text: NS.i18n.series,
-                            style: 'font-size:11px; font-weight:bold; padding:0 4px'
-                        },
-                        { bodyStyle: 'padding:1px 0; border-style:none;	background-color:transparent' },
-                        series
-                    ]
-                },
-                {
-                    xtype: 'container',
-                    bodyStyle: 'border-style:none; background-color:transparent; padding:0',
-                    style: 'margin:0',
-                    items: [
-                        {
-                            xtype: 'label',
-                            text: NS.i18n.category,
-                            style: 'font-size:11px; font-weight:bold; padding:0 4px'
-                        },
-                        { bodyStyle: 'padding:1px 0; border-style:none;	background-color:transparent' },
-                        category
-                    ]
-                },
-                {
-                    xtype: 'container',
-                    bodyStyle: 'border-style:none; background-color:transparent; padding:0',
-                    items: [
-                        {
-                            xtype: 'label',
-                            text: NS.i18n.filters,
-                            style: 'font-size:11px; font-weight:bold; padding:0 4px'
-                        },
-                        { bodyStyle: 'padding:1px 0; border-style:none;	background-color:transparent' },
-                        filter
-                    ]
-                }
-            ]
-        });
-
 		indicatorAvailableStore = Ext.create('Ext.data.Store', {
 			fields: ['id', 'name'],
             lastPage: null,
@@ -4152,13 +4405,13 @@
 				}
 			},
 			store: Ext.create('Ext.data.TreeStore', {
-				fields: ['id', 'name'],
+				fields: ['id', 'name', 'hasChildren'],
 				proxy: {
 					type: 'rest',
 					format: 'json',
 					noCache: false,
 					extraParams: {
-						fields: 'children[id,name,level]'
+						fields: 'children[id,name,children::isNotEmpty|rename(hasChildren)&paging=false'
 					},
 					url: ns.core.init.contextPath + '/api/organisationUnits',
 					reader: {
@@ -4178,15 +4431,9 @@
 				},
 				listeners: {
 					load: function(store, node, records) {
-                        var numberOfLevels = ns.core.init.organisationUnitLevels.length;
-
 						Ext.Array.each(records, function(record) {
-                            //if (Ext.isBoolean(record.data.hasChildren)) {
-                                //record.set('leaf', !record.data.hasChildren);
-                            //}
-
-                            if (Ext.isNumber(numberOfLevels)) {
-                                record.set('leaf', parseInt(record.raw.level) === numberOfLevels);
+                            if (Ext.isBoolean(record.data.hasChildren)) {
+                                record.set('leaf', !record.data.hasChildren);
                             }
                         });
 					}
@@ -4528,7 +4775,8 @@
 		// dimensions
 
 		getDimensionPanel = function(dimension, iconCls) {
-			var	availableStore,
+			var	onSelect,
+                availableStore,
 				selectedStore,
 				available,
 				selected,
@@ -4537,6 +4785,17 @@
 				createPanel,
 				getPanels;
 
+            onSelect = function() {
+                var win = ns.app.layoutWindow;
+
+                if (selectedStore.getRange().length) {
+                    win.addDimension({id: dimension.id, name: dimension.name});
+                }
+                else if (!selectedStore.getRange().length && win.hasDimension(dimension.id)) {
+                    win.removeDimension(dimension.id);
+                }
+            };
+
 			availableStore = Ext.create('Ext.data.Store', {
 				fields: ['id', 'name'],
 				lastPage: null,
@@ -4603,7 +4862,15 @@
 
 			selectedStore = Ext.create('Ext.data.Store', {
 				fields: ['id', 'name'],
-				data: []
+				data: [],
+                listeners: {
+                    add: function() {
+                        onSelect();
+                    },
+                    remove: function() {
+                        onSelect();
+                    }
+                }
 			});
 
 			available = Ext.create('Ext.ux.form.MultiSelect', {
@@ -4809,7 +5076,7 @@
 			items: accordionBody,
 			panels: accordionPanels,
 			setThisHeight: function(mx) {
-				var settingsHeight = 91,
+				var settingsHeight = 46,
 					panelHeight = settingsHeight + this.panels.length * 28,
 					height;
 
@@ -4862,7 +5129,6 @@
 			}(),
 			items: [
                 chartType,
-                layout,
                 accordion
 			],
 			listeners: {
@@ -4872,6 +5138,23 @@
 			}
 		});
 
+		layoutButton = Ext.create('Ext.button.Button', {
+			text: 'Layout',
+			menu: {},
+			handler: function() {
+				if (!ns.app.layoutWindow) {
+					ns.app.layoutWindow = LayoutWindow();
+				}
+
+				ns.app.layoutWindow.show();
+			},
+			listeners: {
+				added: function() {
+					ns.app.layoutButton = this;
+				}
+			}
+		});
+
 		optionsButton = Ext.create('Ext.button.Button', {
 			text: NS.i18n.options,
 			menu: {},
@@ -5159,6 +5442,7 @@
 							update();
 						}
 					},
+					layoutButton,
 					optionsButton,
 					{
 						xtype: 'tbseparator',
@@ -5403,14 +5687,68 @@
 
 			// Layout
 			ns.app.viewport.chartType.setChartType(layout.type);
-
-			ns.app.viewport.series.setValue(xLayout.columnDimensionNames[0]);
-            ns.app.viewport.series.filterNext();
-
-            ns.app.viewport.category.setValue(xLayout.rowDimensionNames[0]);
-            ns.app.viewport.category.filterNext();
-
-            ns.app.viewport.filter.setValue(xLayout.filterDimensionNames);
+            
+			ns.app.stores.dimension.removeAll();
+			ns.app.stores.col.removeAll();
+			ns.app.stores.row.removeAll();
+			ns.app.stores.filter.removeAll();
+
+			if (layout.columns) {
+				dimNames = [];
+
+				for (var i = 0, dim; i < layout.columns.length; i++) {
+					dim = dimConf.objectNameMap[layout.columns[i].dimension];
+
+					if (!Ext.Array.contains(dimNames, dim.dimensionName)) {
+						ns.app.stores.col.add({
+							id: dim.dimensionName,
+							name: dimConf.objectNameMap[dim.dimensionName].name
+						});
+
+						dimNames.push(dim.dimensionName);
+					}
+
+					ns.app.stores.dimension.remove(ns.app.stores.dimension.getById(dim.dimensionName));
+				}
+			}
+
+			if (layout.rows) {
+				dimNames = [];
+
+				for (var i = 0, dim; i < layout.rows.length; i++) {
+					dim = dimConf.objectNameMap[layout.rows[i].dimension];
+
+					if (!Ext.Array.contains(dimNames, dim.dimensionName)) {
+						ns.app.stores.row.add({
+							id: dim.dimensionName,
+							name: dimConf.objectNameMap[dim.dimensionName].name
+						});
+
+						dimNames.push(dim.dimensionName);
+					}
+
+					ns.app.stores.dimension.remove(ns.app.stores.dimension.getById(dim.dimensionName));
+				}
+			}
+
+			if (layout.filters) {
+				dimNames = [];
+
+				for (var i = 0, dim; i < layout.filters.length; i++) {
+					dim = dimConf.objectNameMap[layout.filters[i].dimension];
+
+					if (!Ext.Array.contains(dimNames, dim.dimensionName)) {
+						ns.app.stores.filter.add({
+							id: dim.dimensionName,
+							name: dimConf.objectNameMap[dim.dimensionName].name
+						});
+
+						dimNames.push(dim.dimensionName);
+					}
+
+					ns.app.stores.dimension.remove(ns.app.stores.dimension.getById(dim.dimensionName));
+				}
+			}
 
 			// Options
 			if (ns.app.optionsWindow) {
@@ -5469,12 +5807,10 @@
 		viewport = Ext.create('Ext.container.Viewport', {
 			layout: 'border',
 			chartType: chartType,
-			series: series,
-			category: category,
-			filter: filter,
 			period: period,
 			treePanel: treePanel,
 			setGui: setGui,
+            update: update,
 			items: [
 				westRegion,
 				centerRegion
@@ -5483,6 +5819,9 @@
 				render: function() {
 					ns.app.viewport = this;
 
+                    ns.app.layoutWindow = LayoutWindow();
+                    ns.app.layoutWindow.hide();
+
 					ns.app.optionsWindow = OptionsWindow();
 					ns.app.optionsWindow.hide();
 				},
@@ -5502,7 +5841,7 @@
 						numberOfTabs = ns.core.init.dimensions.length + 5,
 						tabHeight = 28,
 						minPeriodHeight = 380,
-						settingsHeight = 91;
+						settingsHeight = 46;
 
 					if (viewportHeight > numberOfTabs * tabHeight + minPeriodHeight + settingsHeight) {
 						if (!Ext.isIE) {

=== modified file 'dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/core.js'
--- dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/core.js	2014-06-18 11:23:18 +0000
+++ dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/core.js	2014-06-24 10:35:15 +0000
@@ -675,10 +675,12 @@
 						console.log('Response: no valid headers');
 						return;
 					}
-
+                    
 					if (!(Ext.isArray(config.rows) && config.rows.length > 0)) {
-						alert('No values found');
-						return;
+                        if (DV.app) {
+                            alert('No values found');
+                        }
+                        return;
 					}
 
 					if (config.headers.length !== config.rows[0].length) {

=== modified file 'dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/plugin.js'
--- dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/plugin.js	2014-06-18 11:06:05 +0000
+++ dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/plugin.js	2014-06-25 14:03:47 +0000
@@ -702,8 +702,8 @@
 					if (!(Ext.isArray(config.rows) && config.rows.length > 0)) {
                         if (DV.app) {
                             alert('No values found');
-                            return;
                         }
+                        return;
 					}
 
 					if (config.headers.length !== config.rows[0].length) {
@@ -2907,7 +2907,7 @@
 				Ext.data.JsonP.request({
 					url: init.contextPath + '/api/charts/' + id + '.jsonp?fields=' + conf.url.analysisFields.join(','),
 					failure: function(r) {
-						window.open(init.contextPath + '/api/charts/' + id + '.json?fields=' + conf.url.analysisFields.join(',')', '_blank');
+						window.open(init.contextPath + '/api/charts/' + id + '.json?fields=' + conf.url.analysisFields.join(','), '_blank');
 					},
 					success: function(r) {
                         Ext.apply(r, config);