Hi Dave,
PFA updated patch.
On Tue, Nov 21, 2017 at 4:16 PM, Dave Page <[email protected]> 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);