Hi Dave,

PFA updated patch.

On Tue, Nov 21, 2017 at 4:16 PM, Dave Page <dp...@pgadmin.org> wrote:

> HI
>
> On Tue, Nov 21, 2017 at 6:16 AM, Murtuza Zabuawala <murtuza.zabuawala@
> enterprisedb.com> wrote:
>
>> Hi Dave,
>>
>> PFA updated patch with custom tristate boolean editor for SlickGrid to
>> make it compatible with Qt runtime.
>> I have tested it web mode & also modified the feature test accordingly.
>>
>> Also thanks to Neel for testing the patch with latest runtime code.
>>
>
> Cool - so a few thoughts...
>
> - Can we make the null symbol a question mark?
>
​instead of question mark we will make square
gray color
​.​

>
> - I think the feature tests should be enhanced to ensure we verify the
> clickthrough sequence of the new control. I don't think we need a new test
> - maybe just an enhancement to the existing editor test?
>
​Done​


>
> - With a table of the definition shown below, if I add a row with a
> default value for the ID, and false, true, null and hit save, then
> immediately (without refreshing) try to change the first boolean value to
> true and hit save, then I get the following error:
>
> UPDATE public.bools SET
> b1 = %(b1)s::boolean WHERE
> ;
> 2017-11-21 10:34:57,378: ERROR pgadmin:
> Failed to execute query (execute_void) for the server #1 - DB:postgres
> (Query-id: 4249364):
> Error Message:ERROR:  syntax error at or near ";"
> LINE 3: ;
>
>
> Table:
>
> CREATE TABLE public.bools
> (
>     id integer NOT NULL DEFAULT nextval('bools_id_seq'::regclass),
>     b1 boolean,
>     b2 boolean,
>     b3 boolean,
>     CONSTRAINT bools_pkey PRIMARY KEY (id)
> )
>
> ​This issue is not related to given editor changes.
I have opened the separate ticket for the issue
https://redmine.postgresql.org/issues/2886

> Thanks!
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
diff --git a/web/pgadmin/feature_tests/test_data.json 
b/web/pgadmin/feature_tests/test_data.json
index ac00b0b..6c53cc8 100644
--- a/web/pgadmin/feature_tests/test_data.json
+++ b/web/pgadmin/feature_tests/test_data.json
@@ -13,13 +13,14 @@
       "10": ["[61,62]", "[61,62]", "json"],
       "11": ["", "true", "bool"],
       "12": ["", "[null]", "bool"],
-      "13": ["", "[null]", "text[]"],
-      "14": ["{}", "{}", "text[]"],
-      "15": ["{data,,'',\"\",\\'\\',\\\"\\\"}", "{data,[null],,,'',\"\"}", 
"text[]"],
-      "16": ["{}", "{}", "int[]"],
-      "17": ["{123,,456}", "{123,[null],456}", "int[]"],
-      "18": ["", "[null]", "boolean[]"],
-      "19": ["{false,,true}", "{false,[null],true}", "boolean[]"]
+      "13": ["", "false", "bool"],
+      "14": ["", "[null]", "text[]"],
+      "15": ["{}", "{}", "text[]"],
+      "16": ["{data,,'',\"\",\\'\\',\\\"\\\"}", "{data,[null],,,'',\"\"}", 
"text[]"],
+      "17": ["{}", "{}", "int[]"],
+      "18": ["{123,,456}", "{123,[null],456}", "int[]"],
+      "19": ["", "[null]", "boolean[]"],
+      "20": ["{false,,true}", "{false,[null],true}", "boolean[]"]
     }
   }
 }
diff --git a/web/pgadmin/feature_tests/view_data_dml_queries.py 
b/web/pgadmin/feature_tests/view_data_dml_queries.py
index 153d796..e5b0c09 100644
--- a/web/pgadmin/feature_tests/view_data_dml_queries.py
+++ b/web/pgadmin/feature_tests/view_data_dml_queries.py
@@ -65,8 +65,9 @@ CREATE TABLE public.defaults
     text_null4 text COLLATE pg_catalog."default",
     json_defaults json DEFAULT '[51, 52]'::json,
     json_null json,
-    boolean_defaults boolean DEFAULT true,
+    boolean_true boolean DEFAULT true,
     boolean_null boolean,
+    boolean_false boolean,
     text_arr text[],
     text_arr_empty text[],
     text_arr_null text[],
@@ -194,12 +195,17 @@ CREATE TABLE public.defaults
             self.page.find_by_xpath("//*[contains(@class, 'pg_text_editor')]"
                                     "//button[contains(@class, 
'fa-save')]").click()
         else:
+            # Boolean editor test for to True click
             if data[1] == 'true':
-                checkbox_el = cell_el.find_element_by_xpath(".//input")
+                checkbox_el = 
cell_el.find_element_by_xpath(".//*[contains(@class, 'multi-checkbox')]")
                 checkbox_el.click()
-                
ActionChains(self.driver).move_to_element(checkbox_el).double_click(
-                    checkbox_el
-                ).perform()
+            # Boolean editor test for to False click
+            elif data[1] == 'false':
+                checkbox_el = 
cell_el.find_element_by_xpath(".//*[contains(@class, 'multi-checkbox')]")
+                # Sets true
+                checkbox_el.click()
+                # Sets false
+                ActionChains(self.driver).click(checkbox_el).perform()
 
     def _tables_node_expandable(self):
         self.page.toggle_open_tree_item(self.server['name'])
diff --git a/web/pgadmin/static/css/bootstrap.overrides.css 
b/web/pgadmin/static/css/bootstrap.overrides.css
index 73b4fc7..0ea676f 100755
--- a/web/pgadmin/static/css/bootstrap.overrides.css
+++ b/web/pgadmin/static/css/bootstrap.overrides.css
@@ -1346,3 +1346,27 @@ body {
 .alert-dismissable, .alert-dismissible {
   padding-right: 35px !important;
 }
+
+/* CSS for custom checkbox editor in SlickGrid */
+.multi-checkbox .check {
+  display: inline-block;
+  vertical-align: top;
+  width: 15px;
+  height: 15px;
+  border: 1px solid #333;
+  margin: 3px;
+  text-align: center;
+  line-height: 15px;
+}
+
+.multi-checkbox .check.unchecked {
+  background: #fff;
+}
+
+.multi-checkbox .check.partial {
+  background: #cccccc;
+}
+
+.multi-checkbox .check.checked:after {
+  content: "\2713";
+}
diff --git a/web/pgadmin/static/js/slickgrid/editors.js 
b/web/pgadmin/static/js/slickgrid/editors.js
index 80f7cce..ab0bff0 100644
--- a/web/pgadmin/static/js/slickgrid/editors.js
+++ b/web/pgadmin/static/js/slickgrid/editors.js
@@ -12,10 +12,10 @@
         "pgText": pgTextEditor,
         "JsonText": JsonTextEditor,
         "CustomNumber": CustomNumberEditor,
+        "Checkbox": pgCheckboxEditor,
         // Below editor will read only editors, Just to display data
         "ReadOnlyText": ReadOnlyTextEditor,
         "ReadOnlyCheckbox": ReadOnlyCheckboxEditor,
-        "Checkbox": CheckboxEditor, // Override editor to implement checkbox 
with three states
         "ReadOnlypgText": ReadOnlypgTextEditor,
         "ReadOnlyJsonText": ReadOnlyJsonTextEditor
       }
@@ -545,7 +545,7 @@
    */
   function CheckboxEditor(args) {
     var $select, el;
-    var defaultValue;
+    var defaultValue, previousState;
     var scope = this;
 
     this.init = function () {
@@ -564,22 +564,31 @@
           checkbox_status = 1;
         }
         switch(checkbox_status) {
-          // unchecked, going indeterminate
+          // State 0 will come when we had indeterminate state
           case 0:
-            el.prop('indeterminate', true);
-            el.data('checked', 2); // determines next checkbox status
+            // We will check now
+            el.prop('checked', true);
+            el.data('checked', 1);
             break;
 
-          // indeterminate, going checked
+          // State 1 will come when we had checked state
           case 1:
-            el.prop('checked', true);
+            // We will uncheck now
+            el.prop('checked', false);
+            el.data('checked', 2);
+            break;
+
+          // State 2 will come when we had unchecked state
+          case 2:
+            // We will set to indeterminate state
+            el.prop('indeterminate', true);
             el.data('checked', 0);
             break;
 
-          // checked, going unchecked
+          // Default, Set to indeterminate state
           default:
-            el.prop('checked', false);
-            el.data('checked', 1);
+            el.prop('indeterminate', true);
+            el.data('checked', 0);
         }
       });
     };
@@ -594,18 +603,21 @@
 
     this.loadValue = function (item) {
       defaultValue = item[args.column.field];
+      previousState = 0;
       if (_.isNull(defaultValue)||_.isUndefined(defaultValue)) {
         $select.prop('indeterminate', true);
-        $select.data('checked', 2);
+        $select.data('checked', 0);
       }
       else {
         defaultValue = !!item[args.column.field];
         if (defaultValue) {
           $select.prop('checked', true);
-          $select.data('checked', 0);
+          $select.data('checked', 1);
+          previousState = 1;
         } else {
           $select.prop('checked', false);
-          $select.data('checked', 1);
+          $select.data('checked', 2);
+          previousState = 2;
         }
       }
     };
@@ -622,10 +634,8 @@
     };
 
     this.isValueChanged = function () {
-      // var select_value = this.serializeValue();
-      var select_value = $select.data('checked');
-      return (!(select_value === 2 && (defaultValue == null || defaultValue == 
undefined))) &&
-            (select_value !== defaultValue);
+      var currentState = $select.data('checked');
+      return currentState !== previousState;
     };
 
     this.validate = function () {
@@ -1023,4 +1033,87 @@
     this.init();
   }
 
+  // Custom checkbox editor, We need it for runtime as it does not render
+  // indeterminate checkbox state
+  function pgCheckboxEditor(args) {
+    var $select, el;
+    var defaultValue, previousState;
+    var scope = this;
+
+    this.init = function () {
+      $select = $("<div class='multi-checkbox'><span class='check' 
hideFocus></span></div>");
+      $select.appendTo(args.container);
+      $select.focus();
+
+      // The following code is taken from 
https://css-tricks.com/indeterminate-checkboxes/
+      $select.bind("click", function (e) {
+        el = $(this);
+        var states = ["unchecked", "partial", "checked"];
+        var curState = el.find(".check").data("state");
+        curState++;
+        el.find(".check")
+          .removeClass("unchecked partial checked")
+          .addClass(states[curState % states.length])
+          .data("state", curState % states.length);
+      });
+    };
+
+    this.destroy = function () {
+      $select.remove();
+    };
+
+    this.focus = function () {
+      $select.focus();
+    };
+
+    this.loadValue = function (item) {
+      defaultValue = item[args.column.field];
+      previousState = 1;
+      if (_.isNull(defaultValue)||_.isUndefined(defaultValue)) {
+        $select.find(".check").data("state", 1).addClass("partial");
+      }
+      else {
+        defaultValue = !!item[args.column.field];
+        if (defaultValue) {
+          $select.find(".check").data("state", 2).addClass("checked");
+          previousState = 2;
+        } else {
+          $select.find(".check").data("state", 0).addClass("unchecked");
+          previousState = 0;
+        }
+      }
+    };
+
+    this.serializeValue = function () {
+      if ($select.find(".check").data("state") == 1) {
+        return null;
+      }
+      return $select.find(".check").data("state") == 2 ? true : false;
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      var currentState = $select.find(".check").data("state");
+      return currentState !== previousState;
+    };
+
+    this.validate = function () {
+      if (args.column.validator) {
+        var validationResults = args.column.validator(this.serializeValue());
+        if (!validationResults.valid) {
+          return validationResults;
+        }
+      }
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
 })(jQuery);

Reply via email to