You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ak...@apache.org on 2015/07/17 20:37:44 UTC

incubator-ignite git commit: IGNITE-843: Refactored commonFunctions -> $common. Refactored functions for tables -> $table.

Repository: incubator-ignite
Updated Branches:
  refs/heads/ignite-843 8326a4209 -> ca8a48b6d


IGNITE-843: Refactored commonFunctions -> $common. Refactored functions for tables -> $table.


Project: http://git-wip-us.apache.org/repos/asf/incubator-ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ignite/commit/ca8a48b6
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ignite/tree/ca8a48b6
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ignite/diff/ca8a48b6

Branch: refs/heads/ignite-843
Commit: ca8a48b6d5de9bc101846e72696303951beb9ff7
Parents: 8326a42
Author: AKuznetsov <ak...@gridgain.com>
Authored: Sat Jul 18 01:37:33 2015 +0700
Committer: AKuznetsov <ak...@gridgain.com>
Committed: Sat Jul 18 01:37:33 2015 +0700

----------------------------------------------------------------------
 .../nodejs/controllers/admin-controller.js      |  12 +-
 .../controllers/cache-viewer-controller.js      |   2 +-
 .../nodejs/controllers/caches-controller.js     | 197 +++-----------
 .../nodejs/controllers/clusters-controller.js   |  38 ++-
 .../nodejs/controllers/common-module.js         | 259 +++++++++++++++----
 .../nodejs/controllers/metadata-controller.js   | 185 +++----------
 .../nodejs/controllers/models/metadata.json     |   1 +
 .../nodejs/controllers/profile-controller.js    |   6 +-
 .../nodejs/controllers/summary-controller.js    |  14 +-
 .../nodejs/views/includes/controls.jade         |  51 ++--
 10 files changed, 346 insertions(+), 419 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/controllers/admin-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/controllers/admin-controller.js b/modules/web-control-center/nodejs/controllers/admin-controller.js
index dc6957a..09490fe 100644
--- a/modules/web-control-center/nodejs/controllers/admin-controller.js
+++ b/modules/web-control-center/nodejs/controllers/admin-controller.js
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-controlCenterModule.controller('adminController', ['$scope', '$http', '$confirm', 'commonFunctions', function ($scope, $http, $confirm, commonFunctions) {
+controlCenterModule.controller('adminController', ['$scope', '$http', '$common', '$confirm', function ($scope, $http, $common, $confirm) {
     $scope.users = null;
 
     function reload() {
@@ -24,7 +24,7 @@ controlCenterModule.controller('adminController', ['$scope', '$http', '$confirm'
                 $scope.users = data;
             })
             .error(function (errMsg) {
-                commonFunctions.showError(commonFunctions.errorMessage(errMsg));
+                $common.showError($common.errorMessage(errMsg));
             });
     }
 
@@ -41,9 +41,9 @@ controlCenterModule.controller('adminController', ['$scope', '$http', '$confirm'
                     if (i >= 0)
                         $scope.users.splice(i, 1);
 
-                    commonFunctions.showInfo('User has been removed: "' + user.username + '"');
+                    $common.showInfo('User has been removed: "' + user.username + '"');
                 }).error(function (errMsg) {
-                    commonFunctions.showError('Failed to remove user: "' + commonFunctions.errorMessage(errMsg) + '"');
+                    $common.showError('Failed to remove user: "' + $common.errorMessage(errMsg) + '"');
                 });
         });
     };
@@ -56,11 +56,11 @@ controlCenterModule.controller('adminController', ['$scope', '$http', '$confirm'
 
         $http.post('admin/save', {userId: user._id, adminFlag: user.admin}).success(
             function () {
-                commonFunctions.showInfo('Admin right was successfully toggled for user: "' + user.username + '"');
+                $common.showInfo('Admin right was successfully toggled for user: "' + user.username + '"');
 
                 user.adminChanging = false;
             }).error(function (errMsg) {
-                commonFunctions.showError('Failed to toggle admin right for user: "' + commonFunctions.errorMessage(errMsg) + '"');
+                $common.showError('Failed to toggle admin right for user: "' + $common.errorMessage(errMsg) + '"');
 
                 user.adminChanging = false;
             });

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/controllers/cache-viewer-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/controllers/cache-viewer-controller.js b/modules/web-control-center/nodejs/controllers/cache-viewer-controller.js
index 699d746..6e0c130 100644
--- a/modules/web-control-center/nodejs/controllers/cache-viewer-controller.js
+++ b/modules/web-control-center/nodejs/controllers/cache-viewer-controller.js
@@ -52,7 +52,7 @@ var demoResults = [
 
 var demoCaches = ['Users', 'Organizations', 'Cities'];
 
-controlCenterModule.controller('cacheViewerController', ['$scope', '$http', 'commonFunctions', function ($scope, $http, commonFunctions) {
+controlCenterModule.controller('cacheViewerController', ['$scope', '$http', '$common', function ($scope, $http, $common) {
     $scope.results = demoResults;
 
     $scope.caches = demoCaches;

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/controllers/caches-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/controllers/caches-controller.js b/modules/web-control-center/nodejs/controllers/caches-controller.js
index e95890e..138fc38 100644
--- a/modules/web-control-center/nodejs/controllers/caches-controller.js
+++ b/modules/web-control-center/nodejs/controllers/caches-controller.js
@@ -15,10 +15,29 @@
  * limitations under the License.
  */
 
-controlCenterModule.controller('cachesController', ['$scope', '$http', '$saveAs', '$confirm', 'commonFunctions', function ($scope, $http, $saveAs, $confirm, commonFunctions) {
-        $scope.swapSimpleItems = commonFunctions.swapSimpleItems;
-        $scope.joinTip = commonFunctions.joinTip;
-        $scope.getModel = commonFunctions.getModel;
+controlCenterModule.controller('cachesController', ['$scope', '$http', '$common', '$confirm', '$saveAs', '$table', function ($scope, $http, $common, $confirm, $saveAs, $table) {
+        $scope.joinTip = $common.joinTip;
+        $scope.getModel = $common.getModel;
+
+        $scope.tableSimpleNewItem = $table.tableSimpleNewItem;
+        $scope.tableSimpleNewItemActive = $table.tableSimpleNewItemActive;
+        $scope.tableSimpleValid = $table.tableSimpleValid;
+        $scope.tableSimpleSave = $table.tableSimpleSave;
+        $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible;
+        $scope.tableSimpleStartEdit = $table.tableSimpleStartEdit;
+        $scope.tableSimpleEditing = $table.tableSimpleEditing;
+        $scope.tableSimpleRemove = $table.tableSimpleRemove;
+        $scope.tableSimpleUp = $table.tableSimpleUp;
+        $scope.tableSimpleDown = $table.tableSimpleDown;
+        $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible;
+
+        $scope.tablePairNewItem = $table.tablePairNewItem;
+        $scope.tablePairNewItemActive = $table.tablePairNewItemActive;
+        $scope.tablePairSave = $table.tablePairSave;
+        $scope.tablePairSaveVisible = $table.tablePairSaveVisible;
+        $scope.tablePairStartEdit = $table.tablePairStartEdit;
+        $scope.tablePairEditing = $table.tablePairEditing;
+        $scope.tablePairRemove = $table.tablePairRemove;
 
         $scope.atomicities = [
             {value: 'ATOMIC', label: 'ATOMIC'},
@@ -82,13 +101,13 @@ controlCenterModule.controller('cachesController', ['$scope', '$http', '$saveAs'
                 $scope.advanced = data.advanced;
             })
             .error(function (errMsg) {
-                commonFunctions.showError(errMsg);
+                $common.showError(errMsg);
             });
 
         $scope.caches = [];
 
         $scope.required = function (field) {
-            var model = commonFunctions.isDefined(field.path) ? field.path + '.' + field.model : field.model;
+            var model = $common.isDefined(field.path) ? field.path + '.' + field.model : field.model;
 
             var backupItem = $scope.backupItem;
 
@@ -103,161 +122,21 @@ controlCenterModule.controller('cachesController', ['$scope', '$http', '$saveAs'
                 return true;
 
             if (model == 'evictionPolicy.kind' && onHeapTired)
-                return backupItem.swapEnabled || (commonFunctions.isDefined(offHeapMaxMemory) && offHeapMaxMemory >= 0);
+                return backupItem.swapEnabled || ($common.isDefined(offHeapMaxMemory) && offHeapMaxMemory >= 0);
 
             return false;
         };
 
-        $scope.tableSimple = {name: 'none', editIndex: -1};
+        $scope.tablePairValid = function (item, field, keyCls, valCls) {
+            var model = item[field.model];
 
-        $scope.tablePair = {name: 'none', editIndex: -1};
-
-        function tableSimpleReset() {
-            $scope.tableSimple.name = 'none';
-            $scope.tableSimple.editIndex = -1;
-        }
-
-        function tablePairReset() {
-            $scope.tablePair.name = 'none';
-            $scope.tablePair.editIndex = -1;
-        }
-
-        function tableSimpleState(name, editIndex) {
-            $scope.tableSimple.name = name;
-            $scope.tableSimple.editIndex = editIndex;
-
-            tablePairReset();
-        }
-
-        function tablePairState(name, editIndex) {
-            $scope.tablePair.name = name;
-            $scope.tablePair.editIndex = editIndex;
-
-            tableSimpleReset();
-        }
-
-        $scope.tableSimpleNewItem = function (field) {
-            tableSimpleState(field.model, -1);
-        };
-
-        $scope.tablePairNewItem = function (field) {
-            tablePairState(field.model, -1);
-        };
-
-        $scope.tableSimpleNewItemActive = function (field) {
-            return $scope.tableSimple.name == field.model && $scope.tableSimple.editIndex < 0;
-        };
-
-        $scope.tablePairNewItemActive = function (field) {
-            return $scope.tablePair.name == field.model && $scope.tablePair.editIndex < 0;
-        };
-
-        $scope.tableSimpleSave = function (field, newValue, index) {
-            tableSimpleReset();
-
-            var item = $scope.backupItem;
-
-            if (index < 0) {
-                if (item[field.model])
-                    item[field.model].push(newValue);
-                else
-                    item[field.model] = [newValue];
-            }
-            else
-                item[field.model][index] = newValue;
-        };
-
-        function tablePairValid(keyCls, valCls) {
-            if (!keyCls) {
-                commonFunctions.showError('Key class name should be non empty!');
-
-                return false;
-            }
-
-            if (!valCls) {
-                commonFunctions.showError('Value class name should be non empty!');
+            if ($common.isDefined(model) && _.findIndex(model, function (pair) {return pair.keyClass == keyCls}) >= 0) {
+                $common.showError('Indexed type with such key class already exists!');
 
                 return false;
             }
 
             return true;
-        }
-
-        $scope.tablePairSave = function (field, newKey, newValue, index) {
-            if (tablePairValid(newKey, newValue)) {
-                tableSimpleReset();
-                tablePairReset();
-
-                var item = $scope.backupItem;
-
-                var pair = {};
-
-                if (index < 0) {
-                    pair[field.keyName] = newKey;
-                    pair[field.valueName] = newValue;
-
-                    if (item[field.model])
-                        item[field.model].push(pair);
-                    else
-                        item[field.model] = [pair];
-                }
-                else {
-                    pair = item[field.model][index];
-
-                    pair[field.keyName] = newKey;
-                    pair[field.valueName] = newValue;
-                }
-            }
-        };
-
-        $scope.tableSimpleStartEdit = function (field, index) {
-            tableSimpleState(field.model, index);
-
-            return $scope.backupItem[field.model][index];
-        };
-
-        $scope.tablePairStartEdit = function (field, index) {
-            tablePairState(field.model, index);
-
-            return $scope.backupItem[field.model][index];
-        };
-
-        $scope.tableSimpleEditing = function (field, index) {
-            return $scope.tableSimple.name == field.model && $scope.tableSimple.editIndex == index;
-        };
-
-        $scope.tablePairEditing = function (field, index) {
-            return $scope.tablePair.name == field.model && $scope.tablePair.editIndex == index;
-        };
-
-        $scope.tableSimpleRemove = function (field, index) {
-            tableSimpleReset();
-            tablePairReset();
-
-            $scope.backupItem[field.model].splice(index, 1);
-        };
-
-        $scope.tablePairRemove = function (field, index) {
-            tableSimpleReset();
-            tablePairReset();
-
-            $scope.backupItem[field.model].splice(index, 1);
-        };
-
-        $scope.tableSimpleUp = function (field, index) {
-            tableSimpleReset();
-
-            swapSimpleItems($scope.backupItem[field.model], index, index - 1);
-        };
-
-        $scope.tableSimpleDown = function (field, index) {
-            tableSimpleReset();
-
-            swapSimpleItems($scope.backupItem[field.model], index, index + 1);
-        };
-
-        $scope.tableSimpleDownVisible = function (field, index) {
-            return index < $scope.backupItem[field.model].length - 1;
         };
 
         // When landing on the page, get caches and show them.
@@ -293,7 +172,7 @@ controlCenterModule.controller('cachesController', ['$scope', '$http', '$saveAs'
                 }, true);
             })
             .error(function (errMsg) {
-                commonFunctions.showError(errMsg);
+                $common.showError(errMsg);
             });
 
         $scope.selectItem = function (item) {
@@ -312,19 +191,19 @@ controlCenterModule.controller('cachesController', ['$scope', '$http', '$saveAs'
             var cacheStoreFactorySelected = item.cacheStoreFactory && item.cacheStoreFactory.kind;
 
             if (cacheStoreFactorySelected && !(item.readThrough || item.writeThrough)) {
-                commonFunctions.showError('Store is configured but read/write through are not enabled!');
+                $common.showError('Store is configured but read/write through are not enabled!');
 
                 return false;
             }
 
             if ((item.readThrough || item.writeThrough) && !cacheStoreFactorySelected) {
-                commonFunctions.showError('Read / write through are enabled but store is not configured!');
+                $common.showError('Read / write through are enabled but store is not configured!');
 
                 return false;
             }
 
             if (item.writeBehindEnabled && !cacheStoreFactorySelected) {
-                commonFunctions.showError('Write behind enabled but store is not configured!');
+                $common.showError('Write behind enabled but store is not configured!');
 
                 return false;
             }
@@ -350,10 +229,10 @@ controlCenterModule.controller('cachesController', ['$scope', '$http', '$saveAs'
 
                     $scope.selectItem(item);
 
-                    commonFunctions.showInfo('Cache "' + item.name + '" saved.');
+                    $common.showInfo('Cache "' + item.name + '" saved.');
                 })
                 .error(function (errMsg) {
-                    commonFunctions.showError(errMsg);
+                    $common.showError(errMsg);
                 });
         }
 
@@ -388,7 +267,7 @@ controlCenterModule.controller('cachesController', ['$scope', '$http', '$saveAs'
 
                     $http.post('caches/remove', {_id: _id})
                         .success(function () {
-                            commonFunctions.showInfo('Cache has been removed: ' + selectedItem.name);
+                            $common.showInfo('Cache has been removed: ' + selectedItem.name);
 
                             var caches = $scope.caches;
 
@@ -408,7 +287,7 @@ controlCenterModule.controller('cachesController', ['$scope', '$http', '$saveAs'
                             }
                         })
                         .error(function (errMsg) {
-                            commonFunctions.showError(errMsg);
+                            $common.showError(errMsg);
                         });
                 }
             );

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/controllers/clusters-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/controllers/clusters-controller.js b/modules/web-control-center/nodejs/controllers/clusters-controller.js
index af0e0d5..426ac51 100644
--- a/modules/web-control-center/nodejs/controllers/clusters-controller.js
+++ b/modules/web-control-center/nodejs/controllers/clusters-controller.js
@@ -15,13 +15,27 @@
  * limitations under the License.
  */
 
-controlCenterModule.controller('clustersController', ['$scope', '$http', '$saveAs', '$confirm', 'commonFunctions', function ($scope, $http, $saveAs, $confirm, commonFunctions) {
-        $scope.swapSimpleItems = commonFunctions.swapSimpleItems;
-        $scope.joinTip = commonFunctions.joinTip;
-        $scope.getModel = commonFunctions.getModel;
+controlCenterModule.controller('clustersController', ['$scope', '$http', '$common', '$confirm', '$saveAs', '$table', function ($scope, $http, $common, $confirm, $saveAs, $table) {
+        $scope.joinTip = $common.joinTip;
+        $scope.getModel = $common.getModel;
+
+        $scope.tableSimpleNewItem = $table.tableSimpleNewItem;
+        $scope.tableSimpleNewItemActive = $table.tableSimpleNewItemActive;
+        $scope.tableSimpleValid = $table.tableSimpleValid;
+        $scope.tableSimpleSave = $table.tableSimpleSave;
+        $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible;
+        $scope.tableSimpleStartEdit = $table.tableSimpleStartEdit;
+        $scope.tableSimpleEditing = $table.tableSimpleEditing;
+        $scope.tableSimpleRemove = $table.tableSimpleRemove;
+        $scope.tableSimpleUp = $table.tableSimpleUp;
+        $scope.tableSimpleDown = $table.tableSimpleDown;
+        $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible;
 
         $scope.templates = [
-            {value: {discovery: {kind: 'Multicast', Vm: {addresses: ['127.0.0.1:47500..47510']}, Multicast: {}}}, label: 'multicast'},
+            {
+                value: {discovery: {kind: 'Multicast', Vm: {addresses: ['127.0.0.1:47500..47510']}, Multicast: {}}},
+                label: 'multicast'
+            },
             {value: {discovery: {kind: 'Vm', Vm: {addresses: ['127.0.0.1:47500..47510']}}}, label: 'local'}
         ];
 
@@ -94,7 +108,7 @@ controlCenterModule.controller('clustersController', ['$scope', '$http', '$saveA
                 $scope.advanced = data.advanced;
             })
             .error(function (errMsg) {
-                commonFunctions.showError(errMsg);
+                $common.showError(errMsg);
             });
 
         // When landing on the page, get clusters and show them.
@@ -131,7 +145,7 @@ controlCenterModule.controller('clustersController', ['$scope', '$http', '$saveA
                 }, true);
             })
             .error(function (errMsg) {
-                commonFunctions.showError(errMsg);
+                $common.showError(errMsg);
             });
 
         $scope.selectItem = function (item) {
@@ -161,7 +175,7 @@ controlCenterModule.controller('clustersController', ['$scope', '$http', '$saveA
                         var cache = $scope.caches[idx];
 
                         if (cache.swapEnabled) {
-                            commonFunctions.showError('Swap space SPI is not configured, but cache "' + cache.label + '" configured to use swap!');
+                            $common.showError('Swap space SPI is not configured, but cache "' + cache.label + '" configured to use swap!');
 
                             return false;
                         }
@@ -190,10 +204,10 @@ controlCenterModule.controller('clustersController', ['$scope', '$http', '$saveA
 
                     $scope.selectItem(item);
 
-                    commonFunctions.showInfo('Cluster "' + item.name + '" saved.');
+                    $common.showInfo('Cluster "' + item.name + '" saved.');
                 })
                 .error(function (errMsg) {
-                    commonFunctions.showError(errMsg);
+                    $common.showError(errMsg);
                 });
         }
 
@@ -228,7 +242,7 @@ controlCenterModule.controller('clustersController', ['$scope', '$http', '$saveA
 
                     $http.post('clusters/remove', {_id: _id})
                         .success(function () {
-                            commonFunctions.showInfo('Cluster has been removed: ' + selectedItem.name);
+                            $common.showInfo('Cluster has been removed: ' + selectedItem.name);
 
                             var clusters = $scope.clusters;
 
@@ -248,7 +262,7 @@ controlCenterModule.controller('clustersController', ['$scope', '$http', '$saveA
                             }
                         })
                         .error(function (errMsg) {
-                            commonFunctions.showError(errMsg);
+                            $common.showError(errMsg);
                         });
                 }
             );

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/controllers/common-module.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/controllers/common-module.js b/modules/web-control-center/nodejs/controllers/common-module.js
index 7de47a9..28eb2d7 100644
--- a/modules/web-control-center/nodejs/controllers/common-module.js
+++ b/modules/web-control-center/nodejs/controllers/common-module.js
@@ -17,8 +17,47 @@
 
 var controlCenterModule = angular.module('ignite-web-control-center', ['smart-table', 'mgcrea.ngStrap', 'ui.ace', 'ngSanitize']);
 
+// Modal popup configuration.
+controlCenterModule.config(function ($modalProvider) {
+    angular.extend($modalProvider.defaults, {
+        html: true
+    });
+});
+
+// Tooltips configuration.
+controlCenterModule.config(function ($tooltipProvider) {
+    angular.extend($tooltipProvider.defaults, {
+        container: 'body',
+        placement: 'right',
+        html: 'true',
+        trigger: 'click hover'
+    });
+});
+
+// Comboboxes configuration.
+controlCenterModule.config(function ($selectProvider) {
+    angular.extend($selectProvider.defaults, {
+        maxLength: '1',
+        allText: 'Select All',
+        noneText: 'Clear All',
+        templateUrl: '/select',
+        iconCheckmark: 'fa fa-check',
+        caretHtml: '<span class="caret"></span>'
+    });
+});
+
+// Alerts configuration.
+controlCenterModule.config(function ($alertProvider) {
+    angular.extend($alertProvider.defaults, {
+        container: 'body',
+        placement: 'top-right',
+        duration: '5',
+        type: 'danger'
+    });
+});
+
 // Common functions to be used in controllers.
-controlCenterModule.service('commonFunctions', ['$alert', function ($alert) {
+controlCenterModule.service('$common', ['$alert', function ($alert) {
     var msgModal = undefined;
 
     function errorMessage(errMsg) {
@@ -30,7 +69,9 @@ controlCenterModule.service('commonFunctions', ['$alert', function ($alert) {
     }
 
     return {
-        getModel: function (obj, path) {
+        getModel: function (obj, field) {
+            var path = field.path;
+
             if (!isDefined(path))
                 return obj;
 
@@ -51,12 +92,6 @@ controlCenterModule.service('commonFunctions', ['$alert', function ($alert) {
 
             return root;
         },
-        swapSimpleItems: function (a, ix1, ix2) {
-            var tmp = a[ix1];
-
-            a[ix1] = a[ix2];
-            a[ix2] = tmp;
-        },
         joinTip: function (arr) {
             if (!arr) {
                 return arr;
@@ -95,18 +130,13 @@ controlCenterModule.service('commonFunctions', ['$alert', function ($alert) {
     }
 }]);
 
-controlCenterModule.config(function($modalProvider) {
-    angular.extend($modalProvider.defaults, {
-        html: true
-    });
-});
-
-controlCenterModule.service('$confirm', function($modal, $rootScope, $q) {
+// Confirm popup service.
+controlCenterModule.service('$confirm', function ($modal, $rootScope, $q) {
     var scope = $rootScope.$new();
 
     var deferred;
 
-    scope.ok = function() {
+    scope.ok = function () {
         deferred.resolve();
 
         confirmModal.hide();
@@ -116,7 +146,7 @@ controlCenterModule.service('$confirm', function($modal, $rootScope, $q) {
 
     var parentShow = confirmModal.show;
 
-    confirmModal.show = function(content) {
+    confirmModal.show = function (content) {
         scope.content = content || 'Confirm deletion?';
 
         deferred = $q.defer();
@@ -129,12 +159,13 @@ controlCenterModule.service('$confirm', function($modal, $rootScope, $q) {
     return confirmModal;
 });
 
-controlCenterModule.service('$saveAs', function($modal, $rootScope, $q) {
+// "Save as" popup service.
+controlCenterModule.service('$saveAs', function ($modal, $rootScope, $q) {
     var scope = $rootScope.$new();
 
     var deferred;
 
-    scope.ok = function(newName) {
+    scope.ok = function (newName) {
         deferred.resolve(newName);
 
         saveAsModal.hide();
@@ -144,7 +175,7 @@ controlCenterModule.service('$saveAs', function($modal, $rootScope, $q) {
 
     var parentShow = saveAsModal.show;
 
-    saveAsModal.show = function(oldName) {
+    saveAsModal.show = function (oldName) {
         scope.newName = oldName + '(1)';
 
         deferred = $q.defer();
@@ -157,37 +188,157 @@ controlCenterModule.service('$saveAs', function($modal, $rootScope, $q) {
     return saveAsModal;
 });
 
-controlCenterModule.config(function ($tooltipProvider) {
-    angular.extend($tooltipProvider.defaults, {
-        container: 'body',
-        placement: 'right',
-        html: 'true',
-        trigger: 'click hover'
-    });
-});
+// Tables support service.
+controlCenterModule.service('$table', ['$common', function ($common) {
+    var tableSimple = {name: 'none', editIndex: -1};
+    var tablePair = {name: 'none', editIndex: -1};
 
-controlCenterModule.config(function ($selectProvider) {
-    angular.extend($selectProvider.defaults, {
-        maxLength: '1',
-        allText: 'Select All',
-        noneText: 'Clear All',
-        templateUrl: '/select',
-        iconCheckmark: 'fa fa-check',
-        caretHtml: '<span class="caret"></span>'
-    });
-});
+    function swapSimpleItems(a, ix1, ix2) {
+        var tmp = a[ix1];
+
+        a[ix1] = a[ix2];
+        a[ix2] = tmp;
+    }
+
+    function model(item, field) {
+        return $common.getModel(item, field);
+    }
+
+    function tableSimpleReset() {
+        tableSimple.name = 'none';
+        tableSimple.editIndex = -1;
+    }
+
+    function tablePairReset() {
+        tablePair.name = 'none';
+        tablePair.editIndex = -1;
+    }
+
+    function tableSimpleState(name, editIndex) {
+        tableSimple.name = name;
+        tableSimple.editIndex = editIndex;
+
+        tablePairReset();
+    }
+
+    function tablePairState(name, editIndex) {
+        tablePair.name = name;
+        tablePair.editIndex = editIndex;
+
+        tableSimpleReset();
+    }
+
+    return {
+        tableSimpleNewItem: function (field) {
+            tableSimpleState(field.model, -1);
+        },
+        tableSimpleNewItemActive: function (field) {
+            return tableSimple.name == field.model && tableSimple.editIndex < 0;
+        },
+        tableSimpleSave: function (valueValid, item, field, newValue, index) {
+            if (valueValid(newValue)) {
+                tableSimpleReset();
+                tablePairReset();
+
+                if (index < 0) {
+                    if (model(item, field)[field.model])
+                        model(item, field)[field.model].push(newValue);
+                    else
+                        model(item, field)[field.model] = [newValue];
+                }
+                else
+                    model(item, field)[field.model][index] = newValue;
+            }
+        },
+        tableSimpleValid: function(newValue) {
+            return true;
+        },
+        tableSimpleSaveVisible: function(newValue) {
+            return $common.isDefined(newValue) && newValue.trim().length > 0;
+        },
+        tableSimpleStartEdit: function (item, field, index) {
+            tableSimpleState(field.model, index);
+
+            return model(item, field)[field.model][index];
+        },
+        tableSimpleEditing: function (field, index) {
+            return tableSimple.name == field.model && tableSimple.editIndex == index;
+        },
+        tableSimpleRemove: function (item, field, index) {
+            tableSimpleReset();
+            tablePairReset();
+
+            model(item, field)[field.model].splice(index, 1);
+        },
+        tableSimpleUp: function (item, field, index) {
+            tableSimpleReset();
+
+            swapSimpleItems(model(item, field)[field.model], index, index - 1);
+        },
+        tableSimpleDown: function (item, field, index) {
+            tableSimpleReset();
+
+            swapSimpleItems(model(item, field)[field.model], index, index + 1);
+        },
+        tableSimpleDownVisible: function (item, field, index) {
+            return index < model(item, field)[field.model].length - 1;
+        },
+        tablePairNewItem: function (field) {
+            tablePairState(field.model, -1);
+        },
+        tablePairNewItemActive: function (field) {
+            return tablePair.name == field.model && tablePair.editIndex < 0;
+        },
+        tablePairValid: function(item, field, newKey, newValue) {
+            return $common.isDefined(item) && $common.isDefined(newKey) && $common.isDefined(newValue);
+        },
+        tablePairSave: function (pairValid, item, field, newKey, newValue, index) {
+            if (pairValid(item, field, newKey, newValue)) {
+                tableSimpleReset();
+                tablePairReset();
+
+                var pair = {};
+
+                if (index < 0) {
+                    pair[field.keyName] = newKey;
+                    pair[field.valueName] = newValue;
+
+                    if (item[field.model])
+                        item[field.model].push(pair);
+                    else
+                        item[field.model] = [pair];
+                }
+                else {
+                    pair = item[field.model][index];
+
+                    pair[field.keyName] = newKey;
+                    pair[field.valueName] = newValue;
+                }
+            }
+        },
+        tablePairSaveVisible: function(newKey, newValue) {
+            return $common.isDefined(newKey) && $common.isDefined(newValue) &&
+                newKey.trim().length > 0 &&  newValue.trim().length > 0;
+        },
+        tablePairStartEdit: function (item, field, index) {
+            tablePairState(field.model, index);
+
+            return item[field.model][index];
+        },
+        tablePairEditing: function (field, index) {
+            return tablePair.name == field.model && tablePair.editIndex == index;
+        },
+        tablePairRemove: function (item, field, index) {
+            tableSimpleReset();
+            tablePairReset();
+
+            item[field.model].splice(index, 1);
+        }
+    }
+}]);
 
-// Alert settings
-controlCenterModule.config(function ($alertProvider) {
-    angular.extend($alertProvider.defaults, {
-        container: 'body',
-        placement: 'top-right',
-        duration: '5',
-        type: 'danger'
-    });
-});
 
-// Decode name using map(value, label).
+// Filter to decode name using map(value, label).
 controlCenterModule.filter('displayValue', function () {
     return function (v, m, dflt) {
         var i = _.findIndex(m, function (item) {
@@ -207,7 +358,7 @@ controlCenterModule.filter('displayValue', function () {
 });
 
 /**
- * Replaces all occurrences of {@code org.apache.ignite.} with {@code o.a.i.},
+ * Filter for replacing all occurrences of {@code org.apache.ignite.} with {@code o.a.i.},
  * {@code org.apache.ignite.internal.} with {@code o.a.i.i.},
  * {@code org.apache.ignite.internal.visor.} with {@code o.a.i.i.v.} and
  * {@code org.apache.ignite.scalar.} with {@code o.a.i.s.}.
@@ -224,6 +375,7 @@ controlCenterModule.filter('compact', function () {
     }
 });
 
+// Directive to enable validation for IP addresses.
 controlCenterModule.directive('ipaddress', function () {
     const ip = '(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])';
     const port = '([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])';
@@ -243,6 +395,7 @@ controlCenterModule.directive('ipaddress', function () {
     }
 });
 
+// Directive to enable validation to match specified value.
 controlCenterModule.directive('match', function ($parse) {
     return {
         require: 'ngModel',
@@ -256,6 +409,7 @@ controlCenterModule.directive('match', function ($parse) {
     };
 });
 
+// Navigation bar controller.
 controlCenterModule.controller('activeLink', [
     '$scope', function ($scope) {
         $scope.isActive = function (path) {
@@ -263,10 +417,11 @@ controlCenterModule.controller('activeLink', [
         };
     }]);
 
+// Login popup controller.
 controlCenterModule.controller('auth', [
-    '$scope', '$modal', '$alert', '$http', '$window', 'commonFunctions',
-    function ($scope, $modal, $alert, $http, $window, commonFunctions) {
-        $scope.errorMessage = commonFunctions.errorMessage;
+    '$scope', '$modal', '$alert', '$http', '$window', '$common',
+    function ($scope, $modal, $alert, $http, $window, $common) {
+        $scope.errorMessage = $common.errorMessage;
 
         $scope.action = 'login';
 

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/controllers/metadata-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/controllers/metadata-controller.js b/modules/web-control-center/nodejs/controllers/metadata-controller.js
index 2ff419f..1ebbc9d 100644
--- a/modules/web-control-center/nodejs/controllers/metadata-controller.js
+++ b/modules/web-control-center/nodejs/controllers/metadata-controller.js
@@ -15,10 +15,29 @@
  * limitations under the License.
  */
 
-controlCenterModule.controller('metadataController', ['$scope', '$http', 'commonFunctions', function ($scope, $http, commonFunctions) {
-        $scope.swapSimpleItems = commonFunctions.swapSimpleItems;
-        $scope.joinTip = commonFunctions.joinTip;
-        $scope.getModel = commonFunctions.getModel;
+controlCenterModule.controller('metadataController', ['$scope', '$http', '$common', '$table', function ($scope, $http, $common, $table) {
+        $scope.joinTip = $common.joinTip;
+        $scope.getModel = $common.getModel;
+
+        $scope.tableSimpleNewItem = $table.tableSimpleNewItem;
+        $scope.tableSimpleNewItemActive = $table.tableSimpleNewItemActive;
+        $scope.tableSimpleValid = $table.tableSimpleValid;
+        $scope.tableSimpleSave = $table.tableSimpleSave;
+        $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible;
+        $scope.tableSimpleStartEdit = $table.tableSimpleStartEdit;
+        $scope.tableSimpleEditing = $table.tableSimpleEditing;
+        $scope.tableSimpleRemove = $table.tableSimpleRemove;
+        $scope.tableSimpleUp = $table.tableSimpleUp;
+        $scope.tableSimpleDown = $table.tableSimpleDown;
+        $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible;
+
+        $scope.tablePairNewItem = $table.tablePairNewItem;
+        $scope.tablePairNewItemActive = $table.tablePairNewItemActive;
+        $scope.tablePairSave = $table.tablePairSave;
+        $scope.tablePairSaveVisible = $table.tablePairSaveVisible;
+        $scope.tablePairStartEdit = $table.tablePairStartEdit;
+        $scope.tablePairEditing = $table.tablePairEditing;
+        $scope.tablePairRemove = $table.tablePairRemove;
 
         $scope.templates = [
             {value: {kind: 'query'}, label: 'query'},
@@ -200,7 +219,7 @@ controlCenterModule.controller('metadataController', ['$scope', '$http', 'common
                 $scope.metadataDb = data.metadataDb;
             })
             .error(function (errMsg) {
-                commonFunctions.showError(errMsg);
+                $common.showError(errMsg);
             });
 
         // When landing on the page, get metadatas and show them.
@@ -233,7 +252,7 @@ controlCenterModule.controller('metadataController', ['$scope', '$http', 'common
                 }, true);
             })
             .error(function (errMsg) {
-                commonFunctions.showError(errMsg);
+                $common.showError(errMsg);
             });
 
         $scope.selectItem = function (item) {
@@ -253,7 +272,7 @@ controlCenterModule.controller('metadataController', ['$scope', '$http', 'common
 
             $http.post('metadata/save', item)
                 .success(function (_id) {
-                    commonFunctions.showInfo('Metadata "' + item.name + '" saved.');
+                    $common.showInfo('Metadata "' + item.name + '" saved.');
 
                     var idx = _.findIndex($scope.metadatas, function (metadata) {
                         return metadata._id == _id;
@@ -271,7 +290,7 @@ controlCenterModule.controller('metadataController', ['$scope', '$http', 'common
 
                 })
                 .error(function (errMsg) {
-                    commonFunctions.showError(errMsg);
+                    $common.showError(errMsg);
                 });
         };
 
@@ -292,160 +311,20 @@ controlCenterModule.controller('metadataController', ['$scope', '$http', 'common
                     }
                 })
                 .error(function (errMsg) {
-                    commonFunctions.showError(errMsg);
+                    $common.showError(errMsg);
                 });
         };
 
-        $scope.tableSimple = {name: 'none', editIndex: -1};
+        $scope.tablePairValid = function (item, field, name, clsName) {
+            var model = item[field.model];
 
-        $scope.tablePair = {name: 'none', editIndex: -1};
-
-        function tableSimpleReset() {
-            $scope.tableSimple.name = 'none';
-            $scope.tableSimple.editIndex = -1;
-        }
-
-        function tablePairReset() {
-            $scope.tablePair.name = 'none';
-            $scope.tablePair.editIndex = -1;
-        }
-
-        function tableSimpleState(name, editIndex) {
-            $scope.tableSimple.name = name;
-            $scope.tableSimple.editIndex = editIndex;
-
-            tablePairReset();
-        }
-
-        function tablePairState(name, editIndex) {
-            $scope.tablePair.name = name;
-            $scope.tablePair.editIndex = editIndex;
-
-            tableSimpleReset();
-        }
-
-        $scope.tableSimpleNewItem = function (field) {
-            tableSimpleState(field.model, -1);
-        };
-
-        $scope.tablePairNewItem = function (field) {
-            tablePairState(field.model, -1);
-        };
-
-        $scope.tableSimpleNewItemActive = function (field) {
-            return $scope.tableSimple.name == field.model && $scope.tableSimple.editIndex < 0;
-        };
-
-        $scope.tablePairNewItemActive = function (field) {
-            return $scope.tablePair.name == field.model && $scope.tablePair.editIndex < 0;
-        };
-
-        $scope.tableSimpleSave = function (field, newValue, index) {
-            tableSimpleReset();
-
-            var item = $scope.backupItem;
-
-            if (index < 0) {
-                if (item[field.model])
-                    item[field.model].push(newValue);
-                else
-                    item[field.model] = [newValue];
-            }
-            else
-                item[field.model][index] = newValue;
-        };
-
-        function tablePairValid(fld, cls) {
-            if (!fld) {
-                commonFunctions.showError('Field name should be non empty!');
-
-                return false;
-            }
-
-            if (!cls) {
-                commonFunctions.showError('Field class name should be non empty!');
+            if ($common.isDefined(model) && _.findIndex(model, function (pair) {return pair.name == name}) >= 0) {
+                $common.showError('Field with such name already exists!');
 
                 return false;
             }
 
             return true;
-        }
-
-        $scope.tablePairSave = function (field, newKey, newValue, index) {
-            if (tablePairValid(newKey, newValue)) {
-                tableSimpleReset();
-                tablePairReset();
-
-                var item = $scope.backupItem;
-
-                var pair = {};
-
-                if (index < 0) {
-                    pair[field.keyName] = newKey;
-                    pair[field.valueName] = newValue;
-
-                    if (item[field.model])
-                        item[field.model].push(pair);
-                    else
-                        item[field.model] = [pair];
-                }
-                else {
-                    pair = item[field.model][index];
-
-                    pair[field.keyName] = newKey;
-                    pair[field.valueName] = newValue;
-                }
-            }
-        };
-
-        $scope.tableSimpleStartEdit = function (field, index) {
-            tableSimpleState(field.model, index);
-
-            return $scope.backupItem[field.model][index];
-        };
-
-        $scope.tablePairStartEdit = function (field, index) {
-            tablePairState(field.model, index);
-
-            return $scope.backupItem[field.model][index];
-        };
-
-        $scope.tableSimpleEditing = function (field, index) {
-            return $scope.tableSimple.name == field.model && $scope.tableSimple.editIndex == index;
-        };
-
-        $scope.tablePairEditing = function (field, index) {
-            return $scope.tablePair.name == field.model && $scope.tablePair.editIndex == index;
-        };
-
-        $scope.tableSimpleRemove = function (field, index) {
-            tableSimpleReset();
-            tablePairReset();
-
-            $scope.backupItem[field.model].splice(index, 1);
-        };
-
-        $scope.tablePairRemove = function (field, index) {
-            tableSimpleReset();
-            tablePairReset();
-
-            $scope.backupItem[field.model].splice(index, 1);
-        };
-
-        $scope.tableSimpleUp = function (field, index) {
-            tableSimpleReset();
-
-            swapSimpleItems($scope.backupItem[field.model], index, index - 1);
-        };
-
-        $scope.tableSimpleDown = function (field, index) {
-            tableSimpleReset();
-
-            swapSimpleItems($scope.backupItem[field.model], index, index + 1);
-        };
-
-        $scope.tableSimpleDownVisible = function (field, index) {
-            return index < $scope.backupItem[field.model].length - 1;
         };
 
         $scope.selectSchema = function (idx) {

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/controllers/models/metadata.json
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/controllers/models/metadata.json b/modules/web-control-center/nodejs/controllers/models/metadata.json
index dc28d59..7ee0ce5 100644
--- a/modules/web-control-center/nodejs/controllers/models/metadata.json
+++ b/modules/web-control-center/nodejs/controllers/models/metadata.json
@@ -109,6 +109,7 @@
       "type": "table-simple",
       "model": "textFields",
       "hide": "backupItem.kind != 'query'",
+      "placeholder": "Field name",
       "tip": ["TODO."]
     },
     {

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/controllers/profile-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/controllers/profile-controller.js b/modules/web-control-center/nodejs/controllers/profile-controller.js
index ccb2b01..2f57274 100644
--- a/modules/web-control-center/nodejs/controllers/profile-controller.js
+++ b/modules/web-control-center/nodejs/controllers/profile-controller.js
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-controlCenterModule.controller('profileController', ['$scope', '$http', 'commonFunctions', function ($scope, $http, commonFunctions) {
+controlCenterModule.controller('profileController', ['$scope', '$http', '$common', function ($scope, $http, $common) {
     $scope.profileUser = angular.copy($scope.user);
 
     $scope.saveUser = function() {
@@ -35,7 +35,7 @@ controlCenterModule.controller('profileController', ['$scope', '$http', 'commonF
                     email: changeEmail ? email : undefined,
                     newPassword: profile.changePassword ? profile.newPassword : undefined
                 }).success(function (user) {
-                    commonFunctions.showInfo('Profile saved.');
+                    $common.showInfo('Profile saved.');
 
                     if (changeUsername)
                         $scope.user.username = userName;
@@ -43,7 +43,7 @@ controlCenterModule.controller('profileController', ['$scope', '$http', 'commonF
                     if (changeEmail)
                         $scope.user.email = email;
                 }).error(function (err) {
-                    commonFunctions.showError('Failed to save profile: ' + commonFunctions.errorMessage(err));
+                    $common.showError('Failed to save profile: ' + $common.errorMessage(err));
                 });
             }
         }

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/controllers/summary-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/controllers/summary-controller.js b/modules/web-control-center/nodejs/controllers/summary-controller.js
index 7e6f961..7c9cd12 100644
--- a/modules/web-control-center/nodejs/controllers/summary-controller.js
+++ b/modules/web-control-center/nodejs/controllers/summary-controller.js
@@ -15,9 +15,9 @@
  * limitations under the License.
  */
 
-controlCenterModule.controller('summaryController', ['$scope', '$http', 'commonFunctions', function ($scope, $http, commonFunctions) {
-    $scope.joinTip = commonFunctions.joinTip;
-    $scope.getModel = commonFunctions.getModel;
+controlCenterModule.controller('summaryController', ['$scope', '$http', '$common', function ($scope, $http, $common) {
+    $scope.joinTip = $common.joinTip;
+    $scope.getModel = $common.getModel;
 
     $scope.javaClassItems = [
         { label: 'snippet',value: false},
@@ -41,7 +41,7 @@ controlCenterModule.controller('summaryController', ['$scope', '$http', 'commonF
             $scope.screenTip = data.screenTip;
         })
         .error(function (errMsg) {
-            commonFunctions.showError(errMsg);
+            $common.showError(errMsg);
         });
 
     $scope.oss = ['debian:8', 'ubuntu:14.10'];
@@ -102,7 +102,7 @@ controlCenterModule.controller('summaryController', ['$scope', '$http', 'commonF
 
                 $scope.reloadServer();
             }).error(function (errMsg) {
-                commonFunctions.showError('Failed to generate config: ' + errMsg);
+                $common.showError('Failed to generate config: ' + errMsg);
             });
     };
 
@@ -112,7 +112,7 @@ controlCenterModule.controller('summaryController', ['$scope', '$http', 'commonF
                 $scope.xmlClient = data.xmlClient;
                 $scope.javaClient = data.javaClient;
             }).error(function (errMsg) {
-                commonFunctions.showError('Failed to generate config: ' + errMsg);
+                $common.showError('Failed to generate config: ' + errMsg);
             });
     };
 
@@ -133,7 +133,7 @@ controlCenterModule.controller('summaryController', ['$scope', '$http', 'commonF
                 document.body.removeChild(file);
             })
             .error(function (errMsg) {
-                commonFunctions.showError('Failed to generate zip: ' + errMsg);
+                $common.showError('Failed to generate zip: ' + errMsg);
             });
     };
 

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/ca8a48b6/modules/web-control-center/nodejs/views/includes/controls.jade
----------------------------------------------------------------------
diff --git a/modules/web-control-center/nodejs/views/includes/controls.jade b/modules/web-control-center/nodejs/views/includes/controls.jade
index 6afb1e6..a32a9fa 100644
--- a/modules/web-control-center/nodejs/views/includes/controls.jade
+++ b/modules/web-control-center/nodejs/views/includes/controls.jade
@@ -25,17 +25,17 @@ mixin tipLabel(lines)
 mixin ico-exclamation(mdl, err, msg)
     i.fa.fa-exclamation-triangle.form-control-feedback(ng-show='inputForm["#{mdl}"].$error.#{err}' bs-tooltip data-title='#{msg}' type='button')
 
-mixin btn-save(click)
-    i.tipField.fa.fa-floppy-o(ng-click=click)
+mixin btn-save(show, click)
+    i.tipField.fa.fa-floppy-o(ng-show=show ng-click=click)
 
 mixin btn-remove(click)
     i.tipField.fa.fa-remove(ng-click=click)
 
-mixin btn-up(iff, show, click)
-    i.fa.fa-arrow-up(ng-if=iff ng-show=show ng-click=click)
+mixin btn-up(show, click)
+    i.tipField.fa.fa-arrow-up(ng-show=show ng-click=click)
 
-mixin btn-down(iff, show, click)
-    i.fa.fa-arrow-down(ng-if=iff ng-show=show ng-click=click)
+mixin btn-down(show, click)
+    i.tipField.fa.fa-arrow-down(ng-show=show ng-click=click)
 
 mixin table-pair(header, tblMdl, keyFld, valFld, keyPlaceholder, valPlaceholder)
     .col-sm-6
@@ -47,11 +47,11 @@ mixin table-pair(header, tblMdl, keyFld, valFld, keyPlaceholder, valPlaceholder)
             tr.col-sm-12(ng-repeat='item in #{tblMdl}')
                 td.col-sm-6
                     div(ng-show='!tablePairEditing(field, $index)')
-                        a(ng-click='curPair = tablePairStartEdit(field, $index); curKey = curPair.#{keyFld}; curValue = curPair.#{valFld}') {{$index + 1}}) {{item.#{keyFld}}} / {{item.#{valFld}}}
-                        +btn-remove('tablePairRemove(field, $index)')
+                        a(ng-click='curPair = tablePairStartEdit(backupItem, field, $index); curKey = curPair.#{keyFld}; curValue = curPair.#{valFld}') {{$index + 1}}) {{item.#{keyFld}}} / {{item.#{valFld}}}
+                        +btn-remove('tablePairRemove(backupItem, field, $index)')
                     div(ng-show='tablePairEditing(field, $index)')
                         label.labelField {{$index + 1}})
-                        +btn-save('tablePairSave(field, curKey, curValue, $index)')
+                        +btn-save('tablePairSaveVisible(curKey, curValue)', 'tablePairSave(tablePairValid, backupItem, field, curKey, curValue, $index)')
                         .input-tip
                             .col-sm-12
                                 input.form-control.table-form-control(type='text' ng-model='curKey' placeholder=keyPlaceholder)
@@ -59,7 +59,7 @@ mixin table-pair(header, tblMdl, keyFld, valFld, keyPlaceholder, valPlaceholder)
                                 input.form-control.table-form-control(type='text' ng-model='curValue' placeholder=valPlaceholder)
     .settings-row(ng-show='tablePairNewItemActive(field)')
         .col-sm-6
-            +btn-save('tablePairSave(field, newKey, newValue, -1)')
+            +btn-save('tablePairSaveVisible(newKey, newValue)', 'tablePairSave(tablePairValid, backupItem, field, newKey, newValue, -1)')
             .input-tip
                 input.form-control.table-form-control(type='text' ng-model='newKey' placeholder=keyPlaceholder)
                 label &nbsp;/&nbsp;
@@ -69,7 +69,7 @@ mixin table-pair(header, tblMdl, keyFld, valFld, keyPlaceholder, valPlaceholder)
 mixin details-row
     - var lblDetailClasses = ['col-sm-4', 'details-label']
 
-    - var detailMdl = 'getModel(backupItem, detail.path)[detail.model]';
+    - var detailMdl = 'getModel(backupItem, detail)[detail.model]';
     - var detailCommon = {'ng-model': detailMdl, 'ng-required': 'detail.required'};
 
     - var customValidators = {'ng-attr-ipaddress': '{{detail.ipaddress}}'}
@@ -109,7 +109,7 @@ mixin details-row
                 button.form-control(bs-select data-multiple='1' data-placeholder='{{detail.placeholder}}' bs-options='item.value as item.label for item in {{detail.items}}')&attributes(detailCommon)
             +tipField('detail.tip')
         div(ng-switch-when='table-simple')&attributes(detailCommon)
-            div(ng-if="detail.label")
+            div(ng-if='detail.label')
                 label.table-header {{detail.label}}:
                 +tipLabel('detail.tableTip')
             table.col-sm-12.links-edit-details(st-table='#{detailMdl}' ng-show='#{detailMdl}.length > 0')
@@ -117,18 +117,18 @@ mixin details-row
                     tr(ng-repeat='item in #{detailMdl} track by $index')
                         td
                             div(ng-show='!tableSimpleEditing(detail, $index)')
-                                +btn-remove(ng-click='tableSimpleRemove(detail, $index)')
-                                +btn-down('detail.reordering', 'tableSimpleDownVisible(detail, $index)', 'tableSimpleDown(detail, $index)')
-                                +btn-up('detail.reordering', '$index > 0', 'tableSimpleUp(detail, $index)')
+                                +btn-remove('tableSimpleRemove(backupItem, detail, $index)')
+                                +btn-down('detail.reordering && tableSimpleDownVisible(backupItem, detail, $index)', 'tableSimpleDown(backupItem, detail, $index)')
+                                +btn-up('detail.reordering && $index > 0', 'tableSimpleUp(backupItem, detail, $index)')
                                 .input-tip
-                                    a(ng-click='detail.editIdx = $index; curValue = #{detailMdl}[$index]') {{$index + 1}}) {{item}}
+                                    a(ng-click='curValue = tableSimpleStartEdit(backupItem, detail, $index)') {{$index + 1}}) {{item}}
                             div(ng-show='tableSimpleEditing(detail, $index)')
                                 label.labelField {{$index + 1}})
-                                +btn-save('tableSimpleSave(detail, curValue, $index)')
+                                +btn-save('tableSimpleSaveVisible(curValue)', 'tableSimpleSave(tableSimpleValid, backupItem, detail, curValue, $index)')
                                 .input-tip.form-group.has-feedback
                                     input.form-control(name='{{detail.model}}.edit' type='text' ng-model='curValue' placeholder='{{detail.placeholder}}')&attributes(customValidators)
                                     +ico-exclamation('{{detail.model}}.edit', 'ipaddress', 'Invalid address, see help for format description.')
-            button.btn.btn-primary.fieldButton(ng-disabled='!newValue || #{detailMdl}.indexOf(newValue) >= 0' ng-click='tableSimpleSave(detail, newValue, -1)') Add
+            button.btn.btn-primary.fieldButton(ng-disabled='!newValue || #{detailMdl}.indexOf(newValue) >= 0' ng-click='tableSimpleSave(tableSimpleValid, backupItem, detail, newValue, -1)') Add
             +tipField('detail.tip')
             .input-tip.form-group.has-feedback
                 input.form-control(name='{{detail.model}}' type='text' ng-model='newValue' ng-focus='tableSimpleNewItem(detail)' placeholder='{{detail.placeholder}}')&attributes(customValidators)
@@ -138,7 +138,7 @@ mixin form-row
     +form-row-custom(['col-sm-2'], ['col-sm-4'])
 
 mixin form-row-custom(lblClasses, fieldClasses)
-    - var fieldMdl = 'getModel(backupItem, field.path)[field.model]';
+    - var fieldMdl = 'getModel(backupItem, field)[field.model]';
     - var fieldCommon = {'ng-model': fieldMdl, 'ng-required': 'field.required || required(field)'};
     - var fieldRequiredClass = '{true: "required"}[field.required || required(field)]'
     - var fieldHide = '{{field.hide}}'
@@ -207,19 +207,18 @@ mixin form-row-custom(lblClasses, fieldClasses)
                     tr.col-sm-12(ng-repeat='item in #{fieldMdl} track by $index')
                         td.col-sm-6
                             div(ng-show='!tableSimpleEditing(field, $index)')
-                                a(ng-click='curValue = tableSimpleStartEdit(field, $index)') {{$index + 1}}) {{item | compact}}
-                                +btn-remove('tableSimpleRemove(field, $index)')
+                                a(ng-click='curValue = tableSimpleStartEdit(backupItem, field, $index)') {{$index + 1}}) {{item | compact}}
+                                +btn-remove('tableSimpleRemove(backupItem, field, $index)')
+                                +btn-down('field.reordering && tableSimpleDownVisible(backupItem, field, $index)', 'tableSimpleDown(backupItem, field, $index)')
+                                +btn-up('field.reordering && $index > 0', 'tableSimpleUp(backupItem, field, $index)')
                             div(ng-show='tableSimpleEditing(field, $index)')
                                 label.labelField {{$index + 1}})
-                                +btn-save('tableSimpleSave(field, curValue, $index)')
+                                +btn-save('tableSimpleSaveVisible(curValue)', 'tableSimpleSave(tableSimpleValid, backupItem, field, curValue, $index)')
                                 .input-tip
                                     input.form-control(type='text' ng-model='curValue' placeholder='{{field.placeholder}}')
-                        td.col-sm-1(ng-if='field.reordering')
-                            +btn-up(true, '$index > 0', 'tableSimpleUp(field, $index)')
-                            +btn-down(true, 'tableSimpleDownVisible(field, $index)', 'tableSimpleDown(field, $index)')
             .settings-row(ng-show='tableSimpleNewItemActive(field)')
                 .col-sm-6
-                    +btn-save('tableSimpleSave(field, newValue, -1)')
+                    +btn-save('tableSimpleSaveVisible(newValue)', 'tableSimpleSave(tableSimpleValid, backupItem, field, newValue, -1)')
                     .input-tip
                         input.form-control(type='text' ng-model='newValue' placeholder='{{field.placeholder}}')
         div(ng-switch-when='fieldsMetadata' ng-hide=fieldHide)