Subject: PATCH: To fix the issue where user cannot view long data in Query Tool output (pgAdmin4)

From: Murtuza Zabuawala <[email-masked]>
Date: Mon, 12 Sep 2016 16:24:38 +0530

Hi,

PFA patch to fix the issue where user was not able to view long data in the
data grid.
To fix the issue we have added Readonly editors in the grid to view data.
RM#1672, RM#1668

--
Regards,
Murtuza Zabuawala
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
diff --git a/web/pgadmin/static/js/slickgrid/slick.pgadmin.editors.js b/web/pgadmin/static/js/slickgrid/slick.pgadmin.editors.js
index 093416d..6f2984a 100644
--- a/web/pgadmin/static/js/slickgrid/slick.pgadmin.editors.js
+++ b/web/pgadmin/static/js/slickgrid/slick.pgadmin.editors.js
@@ -10,7 +10,12 @@
     "Slick": {
       "Editors": {
         "pgText": pgTextEditor,
-        "JsonText": JsonTextEditor
+        "JsonText": JsonTextEditor,
+        // Below editor will read only editors, Just to display data
+        "ReadOnlyText": ReadOnlyTextEditor,
+        "ReadOnlyCheckbox": ReadOnlyCheckboxEditor,
+        "ReadOnlypgText": ReadOnlypgTextEditor,
+        "ReadOnlyJsonText": ReadOnlyJsonTextEditor
       }
     }
   });
@@ -258,4 +263,352 @@
 
     this.init();
   }
+
+  // Text data type editor
+  function ReadOnlypgTextEditor(args) {
+    var $input, $wrapper;
+    var defaultValue;
+    var scope = this;
+
+    this.init = function () {
+      var $container = $("body");
+
+      $wrapper = $("<DIV style='z-index:10000;position:absolute;background:white;padding:5px;border:3px solid gray; -moz-border-radius:10px; border-radius:10px;'/>")
+          .appendTo($container);
+
+      $input = $("<TEXTAREA hidefocus rows=5 style='backround:white;width:250px;height:80px;border:0;outline:0' readonly>")
+          .appendTo($wrapper);
+
+      $("<DIV style='text-align:right'><BUTTON class='btn btn-danger fa fa-lg fa-times long_text_editor pg-alertify-button'>Cancel</BUTTON></DIV>")
+       .appendTo($wrapper);
+
+      $wrapper.find("button:first").bind("click", this.cancel);
+      $input.bind("keydown", this.handleKeyDown);
+
+      scope.position(args.position);
+      $input.focus().select();
+    };
+
+    this.handleKeyDown = function (e) {
+      if (e.which == $.ui.keyCode.ENTER && e.ctrlKey) {
+        scope.cancel();
+      } else if (e.which == $.ui.keyCode.ESCAPE) {
+        e.preventDefault();
+        scope.cancel();
+      } else if (e.which == $.ui.keyCode.TAB && e.shiftKey) {
+        scope.cancel();
+        e.preventDefault();
+        args.grid.navigatePrev();
+      } else if (e.which == $.ui.keyCode.TAB) {
+        scope.cancel();
+        e.preventDefault();
+        args.grid.navigateNext();
+      }
+    };
+
+    this.cancel = function () {
+      $input.val(defaultValue);
+      args.cancelChanges();
+    };
+
+    this.hide = function () {
+      $wrapper.hide();
+    };
+
+    this.show = function () {
+      $wrapper.show();
+    };
+
+    this.position = function (position) {
+      var _elem_height = $wrapper.parent().find('#datagrid').height(),
+        is_hidden, _position;
+      // We can not display editor partially visible so we will lift it above select column
+      if(position.top > _elem_height) {
+        is_hidden = position.bottom - _elem_height;
+      }
+
+      if(is_hidden) {
+        _position = position.top - is_hidden;
+      } else {
+        _position = position.top - 5;
+      }
+
+      $wrapper
+          .css("top", _position)
+          .css("left", position.left - 5)
+    };
+
+    this.destroy = function () {
+      $wrapper.remove();
+    };
+
+    this.focus = function () {
+      $input.focus();
+    };
+
+    this.loadValue = function (item) {
+      $input.val(defaultValue = item[args.column.field]);
+      $input.select();
+    };
+
+    this.serializeValue = function () {
+      return $input.val();
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
+    };
+
+    this.validate = function () {
+      if (args.column.validator) {
+        var validationResults = args.column.validator($input.val());
+        if (!validationResults.valid) {
+          return validationResults;
+        }
+      }
+
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
+  // JSON data type editor
+  function ReadOnlyJsonTextEditor(args) {
+    var $input, $wrapper;
+    var defaultValue;
+    var scope = this;
+
+    this.init = function () {
+      var $container = $("body");
+
+      $wrapper = $("<DIV style='z-index:10000;position:absolute;background:white;padding:5px;border:3px solid gray; -moz-border-radius:10px; border-radius:10px;'/>")
+          .appendTo($container);
+
+      $input = $("<TEXTAREA hidefocus rows=5 style='backround:white;width:250px;height:80px;border:0;outline:0' readonly>")
+          .appendTo($wrapper);
+
+      $("<DIV style='text-align:right'><BUTTON class='btn btn-danger fa fa-lg fa-times long_text_editor pg-alertify-button'>Cancel</BUTTON></DIV>")
+       .appendTo($wrapper);
+
+      $wrapper.find("button:first").bind("click", this.cancel);
+      $input.bind("keydown", this.handleKeyDown);
+
+      scope.position(args.position);
+      $input.focus().select();
+    };
+
+    this.handleKeyDown = function (e) {
+      if (e.which == $.ui.keyCode.ENTER && e.ctrlKey) {
+        scope.cancel();
+      } else if (e.which == $.ui.keyCode.ESCAPE) {
+        e.preventDefault();
+        scope.cancel();
+      } else if (e.which == $.ui.keyCode.TAB && e.shiftKey) {
+        scope.cancel();
+        e.preventDefault();
+        args.grid.navigatePrev();
+      } else if (e.which == $.ui.keyCode.TAB) {
+        scope.cancel();
+        e.preventDefault();
+        args.grid.navigateNext();
+      }
+    };
+
+    this.cancel = function () {
+      $input.val(defaultValue);
+      args.cancelChanges();
+    };
+
+    this.hide = function () {
+      $wrapper.hide();
+    };
+
+    this.show = function () {
+      $wrapper.show();
+    };
+
+    this.position = function (position) {
+      var _elem_height = $wrapper.parent().find('#datagrid').height(),
+        is_hidden, _position;
+      // We can not display editor partially visible so we will lift it above select column
+      if(position.top > _elem_height) {
+        is_hidden = position.bottom - _elem_height;
+      }
+
+      if(is_hidden) {
+        _position = position.top - is_hidden;
+      } else {
+        _position = position.top - 5;
+      }
+
+      $wrapper
+          .css("top", _position)
+          .css("left", position.left - 5)
+    };
+
+    this.destroy = function () {
+      $wrapper.remove();
+    };
+
+    this.focus = function () {
+      $input.focus();
+    };
+
+    this.loadValue = function (item) {
+      var data = defaultValue = item[args.column.field];
+      if (typeof data === "object" && !Array.isArray(data)) {
+        data = JSON.stringify(data);
+      }
+      $input.val(data);
+      $input.select();
+    };
+
+    this.serializeValue = function () {
+      return $input.val();
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
+    };
+
+    this.validate = function () {
+      if (args.column.validator) {
+        var validationResults = args.column.validator($input.val());
+        if (!validationResults.valid) {
+          return validationResults;
+        }
+      }
+
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
+  function ReadOnlyTextEditor(args) {
+    var $input;
+    var defaultValue;
+    var scope = this;
+
+    this.init = function () {
+      $input = $("<INPUT type=text class='editor-text' readonly/>")
+          .appendTo(args.container)
+          .bind("keydown.nav", function (e) {
+            if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
+              e.stopImmediatePropagation();
+            }
+          })
+          .focus()
+          .select();
+    };
+
+    this.destroy = function () {
+      $input.remove();
+    };
+
+    this.focus = function () {
+      $input.focus();
+    };
+
+    this.getValue = function () {
+      return $input.val();
+    };
+
+    this.loadValue = function (item) {
+      defaultValue = item[args.column.field] || "";
+      $input.val(defaultValue);
+      $input[0].defaultValue = defaultValue;
+      $input.select();
+    };
+
+    this.serializeValue = function () {
+      return $input.val();
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
+    };
+
+    this.validate = function () {
+      if (args.column.validator) {
+        var validationResults = args.column.validator($input.val());
+        if (!validationResults.valid) {
+          return validationResults;
+        }
+      }
+
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
+  function ReadOnlyCheckboxEditor(args) {
+    var $select;
+    var defaultValue;
+    var scope = this;
+
+    this.init = function () {
+      $select = $("<INPUT type=checkbox value='true' class='editor-checkbox' hideFocus readonly>");
+      $select.appendTo(args.container);
+      $select.focus();
+    };
+
+    this.destroy = function () {
+      $select.remove();
+    };
+
+    this.focus = function () {
+      $select.focus();
+    };
+
+    this.loadValue = function (item) {
+      defaultValue = !!item[args.column.field];
+      if (defaultValue) {
+        $select.prop('checked', true);
+      } else {
+        $select.prop('checked', false);
+      }
+    };
+
+    this.serializeValue = function () {
+      return $select.prop('checked');
+    };
+
+    this.isValueChanged = function () {
+      return (this.serializeValue() !== defaultValue);
+    };
+
+    this.validate = function () {
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
 })(jQuery);
diff --git a/web/pgadmin/static/js/slickgrid/slick.pgadmin.formatters.js b/web/pgadmin/static/js/slickgrid/slick.pgadmin.formatters.js
index 6740ede..b131657 100644
--- a/web/pgadmin/static/js/slickgrid/slick.pgadmin.formatters.js
+++ b/web/pgadmin/static/js/slickgrid/slick.pgadmin.formatters.js
@@ -11,6 +11,7 @@
     "Slick": {
       "Formatters": {
         "JsonString": JsonFormatter,
+        "Numbers": NumbersFormatter,
         "Checkmark": CheckmarkFormatter
       }
     }
@@ -29,7 +30,18 @@
     }
   }
 
+  function NumbersFormatter(row, cell, value, columnDef, dataContext) {
+    if (value == null || value === "") {
+      return "";
+    } else {
+      return "<span style='float:right'>" + value + "</span>";
+    }
+  }
+
   function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
+    if (value == null || value === "") {
+      return "";
+    }
     return value ? "true" : "false";
   }
 
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index eac64b0..402c316 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -276,8 +276,8 @@ define(
               var notify = false, msg;
               if(self.handler.can_edit) {
                 var data_store = self.handler.data_store;
-                if(_.size(data_store.added) ||
-                    _.size(data_store.updated)) {
+                if(data_store && (_.size(data_store.added) ||
+                    _.size(data_store.updated))) {
                   msg = '{{ _('The data has been modified, but not saved. Are you sure you wish to discard the changes?') }}';
                   notify = true;
                 }
@@ -515,9 +515,7 @@ define(
           collection = [];
         }
 
-        // Create an array for client filter
-        var filter_array = new Array(),
-          grid_columns = new Array(),
+        var grid_columns = new Array(),
           checkboxSelector;
 
           checkboxSelector = new Slick.CheckboxSelectColumn({
@@ -527,37 +525,35 @@ define(
           grid_columns.push(checkboxSelector.getColumnDefinition());
 
         _.each(columns, function(c) {
-            filter_array.push(c.name);
             var options = {
               id: c.name,
               field: c.name,
               name: c.label
             };
 
-            // If grid is editable then add editor
-            if(is_editable) {
-              if(c.cell == 'Json') {
-                options['editor'] = Slick.Editors.JsonText;
-              } else if(c.cell == 'number') {
-                options['editor'] = Slick.Editors.Text;
-              } else if(c.cell == 'boolean') {
-                options['editor'] = Slick.Editors.Checkbox;
-              } else {
-                options['editor'] = Slick.Editors.pgText;
-              }
-            }
-
-            // To handle json & boolean formatter in grid
+            // If grid is editable then add editor else make it readonly
             if(c.cell == 'Json') {
+              options['editor'] = is_editable ? Slick.Editors.JsonText
+                                              : Slick.Editors.ReadOnlyJsonText;
               options['formatter'] = Slick.Formatters.JsonString;
+            } else if(c.cell == 'number') {
+              options['editor'] = is_editable ? Slick.Editors.Text
+                                              : Slick.Editors.ReadOnlyText;
+              options['formatter'] = Slick.Formatters.Numbers;
             } else if(c.cell == 'boolean') {
+              options['editor'] = is_editable ? Slick.Editors.Checkbox
+                                              : Slick.Editors.ReadOnlyCheckbox;
               options['formatter'] = Slick.Formatters.Checkmark;
+            } else {
+              options['editor'] = is_editable ? Slick.Editors.pgText
+                                              : Slick.Editors.ReadOnlypgText;
             }
-            grid_columns.push(options)
+
+           grid_columns.push(options)
         });
 
         var grid_options = {
-          editable: is_editable,
+          editable: true,
           enableAddRow: is_editable,
           enableCellNavigation: true,
           enableColumnReorder: false,

-- 
Sent via pgadmin-hackers mailing list ([email-masked])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

---

From: Dave Page <[email-masked]>
Date: Mon, 12 Sep 2016 12:45:31 +0100

Thanks, applied.

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company