https://fedorahosted.org/freeipa/ticket/4554

[PATCH] 784 webui: refactor combobox search to support search providers

Comboboxe's search logic was moved into a separate object - a search provider. This change makes Entity Select a regular combobox with a special provider.

new JointCBSearchProvider allows to combine results of two providers into a single result.

[PATCH] 785 webui: offer suffixes of trusted domains for overrides

[PATCH] 786 webui: unable to select single value in CB by enter key

Fix: If editable combobox has one value, the value is selected and changed by hand, it can't be re-selected by enter key.
--
Petr Vobornik
From 091139c851c18155f3b3d75afafbb202a4b12859 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Wed, 12 Nov 2014 14:58:46 +0100
Subject: [PATCH] webui: offer suffixes of trusted domains for overrides

https://fedorahosted.org/freeipa/ticket/4554
---
 install/ui/src/freeipa/idviews.js | 86 ++++++++++++++++++++++++++++++++++-----
 1 file changed, 76 insertions(+), 10 deletions(-)

diff --git a/install/ui/src/freeipa/idviews.js b/install/ui/src/freeipa/idviews.js
index 823936401653096a0a649282e3b18f573de09804..f85aedebc0b2b50773a183c1b7a7648791ec3bbf 100644
--- a/install/ui/src/freeipa/idviews.js
+++ b/install/ui/src/freeipa/idviews.js
@@ -20,7 +20,10 @@
  */
 
 define([
+        'dojo/_base/declare',
+        'dojo/Deferred',
         'dojo/on',
+        'dojo/when',
         './ipa',
         './jquery',
         './menu',
@@ -30,9 +33,11 @@ define([
         './text',
         './details',
         './facet',
+        './widget',
         './search',
         './entity'],
-            function(on, IPA, $, menu, phases, reg, rpc, text, mod_details, mod_facet) {
+            function(declare, Deferred, on, when, IPA, $, menu, phases,
+                reg, rpc, text, mod_details, mod_facet, mod_widget) {
 /**
  * ID Views module
  * @class
@@ -274,21 +279,36 @@ return {
         ],
         fields: [
             {
-                $type: 'entity_select',
+                $type: 'combobox',
                 label: '@i18n:objects.idoverrideuser.anchor_label',
                 name: 'ipaanchoruuid',
-                other_entity: 'user',
-                other_field: 'uid',
                 editable: true,
-                tooltip: '@i18n:objects.idoverrideuser.anchor_tooltip'
+                tooltip: '@i18n:objects.idoverrideuser.anchor_tooltip',
+                search_provider: {
+                    $ctor: mod_widget.JointCBSearchProvider,
+                    providers: [
+                        {
+                            $ctor: idviews.TrustSuffixCBSearchProvider
+                        },
+                        {
+                            $ctor: mod_widget.EntityCBSearchProvider,
+                            entity: 'user'
+                        }
+                    ]
+                }
             },
             {
+                $type: 'combobox',
                 label: '@i18n:objects.idoverrideuser.anchor_label',
                 name: 'ipaanchoruuid_default',
                 param: 'ipaanchoruuid',
                 tooltip: '@i18n:objects.idoverrideuser.anchor_tooltip_ad',
                 visible: false,
-                enabled: false
+                enabled: false,
+                editable: true,
+                search_provider: {
+                    $ctor: idviews.TrustSuffixCBSearchProvider
+                }
             },
             'uid',
             'gecos',
@@ -361,18 +381,33 @@ return {
                 $type: 'entity_select',
                 label: '@i18n:objects.idoverridegroup.anchor_label',
                 name: 'ipaanchoruuid',
-                other_entity: 'group',
-                other_field: 'cn',
                 editable: true,
-                tooltip: '@i18n:objects.idoverridegroup.anchor_tooltip'
+                tooltip: '@i18n:objects.idoverridegroup.anchor_tooltip',
+                search_provider: {
+                    $ctor: mod_widget.JointCBSearchProvider,
+                    providers: [
+                        {
+                            $ctor: idviews.TrustSuffixCBSearchProvider
+                        },
+                        {
+                            $ctor: mod_widget.EntityCBSearchProvider,
+                            entity: 'group'
+                        }
+                    ]
+                }
             },
             {
+                $type: 'combobox',
                 label: '@i18n:objects.idoverridegroup.anchor_label',
                 name: 'ipaanchoruuid_default',
                 param: 'ipaanchoruuid',
                 tooltip: '@i18n:objects.idoverridegroup.anchor_tooltip_ad',
                 visible: false,
-                enabled: false
+                enabled: false,
+                editable: true,
+                search_provider: {
+                    $ctor: idviews.TrustSuffixCBSearchProvider
+                }
             },
             'cn',
             'gidnumber',
@@ -455,6 +490,37 @@ idviews.idoverride_adder_policy = function (spec) {
 };
 
 /**
+ * Search provider which returns trusted domains in form of suffixes
+ * @class
+ * @extends widget.EntityCBSearchProvider
+ */
+idviews.TrustSuffixCBSearchProvider = declare([mod_widget.EntityCBSearchProvider], {
+
+    /**
+     * @inheritDoc
+     * @protected
+     */
+    _search: function(filter) {
+        if (!IPA.trust_enabled) return [];
+        var def = new Deferred();
+        when(this.inherited(arguments), function(results) {
+            for (var i=0, l=results.length; i<l; i++) {
+                results[i] = '@'+results[i];
+            }
+            def.resolve(results);
+        }, function(error) {
+            def.reject(error);
+        });
+        return def;
+    },
+
+    constructor: function(spec) {
+        this.entity = this.entity || reg.entity.get('trust');
+        this.param = this.param || 'cn';
+    }
+});
+
+/**
  * Apply Id view on hosts on hostgroup action base class
  *
  * @class idviews.apply_action
-- 
1.9.3

From feb564d167fc92492b831191eaab7235800c9258 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Wed, 12 Nov 2014 15:41:44 +0100
Subject: [PATCH] webui: unable to select single value in CB by enter key

Fix: If editable combobox has one value, the value is selected and changed by hand, it can't be re-selected by enter key.
---
 install/ui/src/freeipa/widget.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index abfdedfaf4e04feadb80d8d40f23e01770260282..f65dc47a0bb827f811cebb6df5ba2ee6117d7f54 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -4006,6 +4006,7 @@ IPA.combobox_widget = function(spec) {
     that.list_on_keyup = function(e) {
         if (e.which === keys.ENTER || e.which === keys.SPACE) {
             e.stopPropagation();
+            that.list_on_change();
             that.close();
             IPA.select_range(that.input, 0, 0);
             return false;
-- 
1.9.3

From 92ad60826aa20089295ac9152441cddc84a34b2b Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Wed, 5 Nov 2014 19:47:06 +0100
Subject: [PATCH] webui: refactor combobox search to support search providers

Comboboxe's search logic was moved into a separate object - a search provider.  This change makes Entity Select a regular combobox with a special provider.

new JointCBSearchProvider allows to combine results of two providers into a single result.
---
 install/ui/doc/categories.json   |   6 +
 install/ui/doc/config.json       |   2 +-
 install/ui/src/freeipa/entity.js |   4 +-
 install/ui/src/freeipa/host.js   |  33 ++---
 install/ui/src/freeipa/trust.js  |  38 +++---
 install/ui/src/freeipa/widget.js | 262 ++++++++++++++++++++++++++++++---------
 6 files changed, 239 insertions(+), 106 deletions(-)

diff --git a/install/ui/doc/categories.json b/install/ui/doc/categories.json
index c84077682eafa42981e8a1c1a2f93c712e6421fd..1e99fb3e55a294be6e1445dcba01174614bedbb6 100644
--- a/install/ui/doc/categories.json
+++ b/install/ui/doc/categories.json
@@ -223,6 +223,12 @@
                     "field.validator",
                     "*_validator"
                 ]
+            },
+            {
+                "name": "Combobox search providers",
+                "classes": [
+                    "*CBSearchProvider"
+                ]
             }
         ]
     },
diff --git a/install/ui/doc/config.json b/install/ui/doc/config.json
index 6a200f9ae673b599e4536a5974ed219bbfe85f0c..a702a80a054683100f2f6435f6eaa7a398ba66a3 100644
--- a/install/ui/doc/config.json
+++ b/install/ui/doc/config.json
@@ -4,7 +4,7 @@
     "--guides": "guides.json",
     "--css": ["doc.css"],
     "--external": ["jQuery", "Store", "QueryResult", "Stateful", "Evented",
-                   "XMLHttpRequest", "Promise"],
+                   "XMLHttpRequest", "Promise", "Deferred"],
     "--warnings": ["-link", "-nodoc"],
     "--": [
         "../src/freeipa/"
diff --git a/install/ui/src/freeipa/entity.js b/install/ui/src/freeipa/entity.js
index eb2d98a558b865b490fc56897f6fb485f87c6c81..171bb2b0d2573f11382f59c718981cbc9a5cfd4b 100644
--- a/install/ui/src/freeipa/entity.js
+++ b/install/ui/src/freeipa/entity.js
@@ -206,7 +206,9 @@ exp.entity = IPA.entity = function(spec) {
     that.dialog_build_overrides = {
         $pre_ops: [
             function (spec, context) {
-                spec.entity = context.entity;
+                if (context.entity) {
+                    spec.entity = context.entity;
+                }
                 return spec;
             }
         ],
diff --git a/install/ui/src/freeipa/host.js b/install/ui/src/freeipa/host.js
index 455ff8f50ec58104d4e046ec0fabf2a7e89eeeb2..20b6fede8331030ee78f1d8954cf9f5055b86405 100644
--- a/install/ui/src/freeipa/host.js
+++ b/install/ui/src/freeipa/host.js
@@ -390,13 +390,18 @@ IPA.host_fqdn_widget = function(spec) {
             required: true
         },
         {
-            $type: 'dnszone_select',
+            $type: 'entity_select',
             name: 'dnszone',
             label: '@mo:dnszone.label_singular',
             editable: true,
             empty_option: false,
             required: true,
-            searchable: true
+            searchable: true,
+            other_entity: 'dnszone',
+            other_field: 'idnsname',
+            filter_options: {
+                forward_only: true
+            }
         }
     ];
 
@@ -581,28 +586,6 @@ IPA.host_deleter_dialog = function(spec) {
     return that;
 };
 
-IPA.dnszone_select_widget = function(spec) {
-
-    spec = spec || {};
-    spec.other_entity = 'dnszone';
-    spec.other_field = 'idnsname';
-
-    var that = IPA.entity_select_widget(spec);
-
-    that.create_search_command = function(filter) {
-        return rpc.command({
-            entity: that.other_entity.name,
-            method: 'find',
-            args: [filter],
-            options: {
-                forward_only: true
-            }
-        });
-    };
-
-    return that;
-};
-
 IPA.host_dnsrecord_entity_link_widget = function(spec) {
 
     var that = IPA.link_widget(spec);
@@ -929,8 +912,6 @@ exp.register = function() {
     e.register({type: 'host', spec: exp.entity_spec});
     f.register('host_fqdn', IPA.host_fqdn_field);
     w.register('host_fqdn', IPA.host_fqdn_widget);
-    f.register('dnszone_select', IPA.field);
-    w.register('dnszone_select', IPA.dnszone_select_widget);
     f.register('host_dnsrecord_entity_link', IPA.field);
     w.register('host_dnsrecord_entity_link', IPA.host_dnsrecord_entity_link_widget);
     f.register('force_host_add_checkbox', IPA.checkbox_field);
diff --git a/install/ui/src/freeipa/trust.js b/install/ui/src/freeipa/trust.js
index 51cfefb99fe10010385b528af209ad535e88b673..ba30d0a3b7c830a2ca0c753a44c3330824308965 100644
--- a/install/ui/src/freeipa/trust.js
+++ b/install/ui/src/freeipa/trust.js
@@ -24,11 +24,12 @@ define([
         './menu',
         './phases',
         './reg',
+        './widget',
         './details',
         './search',
         './association',
         './entity'],
-            function(IPA, $, menu, phases, reg) {
+            function(IPA, $, menu, phases, reg, mod_widget) {
 
 var exp = IPA.trust = {};
 
@@ -348,13 +349,23 @@ return {
                         'ipantflatname',
                         'ipantdomainguid',
                         {
-                            $type: 'trust_fallbackgroup_select',
+                            $type: 'combobox',
                             name: 'ipantfallbackprimarygroup',
-                            other_entity: 'group',
-                            other_field: 'cn',
                             empty_option: false,
-                            filter_options: {
-                                posix: true
+                            search_provider: {
+                                $ctor: mod_widget.JointCBSearchProvider,
+                                providers: [
+                                    {
+                                        options: ['Default SMB Group']
+                                    },
+                                    {
+                                        $ctor: mod_widget.EntityCBSearchProvider,
+                                        entity: 'group',
+                                        filter_options: {
+                                            posix: true
+                                        }
+                                    }
+                                ]
                             }
                         }
                     ]
@@ -395,18 +406,6 @@ IPA.trust.config_details_facet = function(spec) {
     return that;
 };
 
-IPA.trust.fallbackgroup_select_widget = function(spec) {
-    var that = IPA.entity_select_widget(spec);
-
-    that.set_options = function(options) {
-        // always add 'Default SMB Group', it can't be obtained by group-find.
-        options.unshift('Default SMB Group');
-        that.entity_select_set_options(options);
-    };
-
-    return that;
-};
-
 exp.remove_menu_item = function() {
     if (!IPA.trust_enabled) {
         menu.remove_item('ipaserver/trusts');
@@ -426,9 +425,6 @@ IPA.trust.register = function() {
     e.register({type: 'trust', spec: exp.trust_spec});
     e.register({type: 'trustdomain', spec: exp.trustdomain_spec});
     e.register({type: 'trustconfig', spec: exp.trustconfig_spec});
-
-    w.register('trust_fallbackgroup_select', IPA.trust.fallbackgroup_select_widget);
-    f.register('trust_fallbackgroup_select', IPA.field);
 };
 
 phases.on('registration', IPA.trust.register);
diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index 9240df8ef5402310ec9ceafd0b766def10c8cb48..abfdedfaf4e04feadb80d8d40f23e01770260282 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -23,14 +23,18 @@
 
 
 define(['dojo/_base/array',
+       'dojo/_base/declare',
        'dojo/_base/lang',
        'dojo/dom-construct',
+       'dojo/Deferred',
        'dojo/Evented',
        'dojo/has',
        'dojo/keys',
        'dojo/on',
+       'dojo/promise/all',
        'dojo/string',
        'dojo/topic',
+       'dojo/when',
        './builder',
        './config',
        './datetime',
@@ -45,7 +49,7 @@ define(['dojo/_base/array',
        './util',
        'exports'
        ],
-       function(array, lang, construct, Evented, has, keys, on, string, topic, builder, config,
+       function(array, declare, lang, construct, Deferred, Evented, has, keys, on, all, string, topic, when, builder, config,
                 datetime, entity_mod, IPA, $, navigation, phases, reg, rpc, text, util, exp) {
 
 /**
@@ -3558,6 +3562,187 @@ IPA.attribute_table_widget = function(spec) {
 };
 
 /**
+ * Combobox search provider
+ *
+ * Events: 'success', 'error'
+ *
+ * @class widget.CBSearchProvider
+ */
+exp.CBSearchProvider = declare([Evented], {
+
+    /**
+     * Options
+     * @type {Array}
+     */
+    options: [],
+
+    /**
+     * Execute search
+     * @param  {string} filter Filter text
+     * @return {Deferred}
+     */
+    search: function(filter) {
+        var def = this._search(filter);
+        var that = this;
+        when(def, function(result){
+            that.emit('success', { source: that, result: result });
+        }, function(err){
+            that.emit('error', { source: that, error: err });
+        });
+        return def;
+    },
+
+    /**
+     * Actual search
+     * @protected
+     * @param  {string} filter
+     * @return {Deferred}
+     */
+    _search: function(filter) {
+        return this.options;
+    },
+
+    constructor: function(spec) {
+        if (spec.options) this.options = spec.options;
+    }
+});
+
+/**
+ * Entity Combobox search provider
+ *
+ * @class widget.EntityCBSearchProvider
+ * @extends widget.CBSearchProvider
+ */
+exp.EntityCBSearchProvider = declare([exp.CBSearchProvider], {
+
+    /**
+     * Entity
+     * @property {IPA.entity|string}
+     */
+    entity: null,
+
+    /**
+     * Entity param to select as value
+     * @property {string}
+     */
+    param: null,
+
+    /**
+     * Filter for RPC search
+     * @property {Object}
+     */
+    filter_options: {},
+
+    /**
+     * @inheritDoc
+     * @protected
+     */
+    _search: function(filter) {
+        var def = new Deferred();
+        var command = this.create_search_command(filter);
+        command.on_success = lang.hitch(this, function(data, text_status, xhr) {
+            var options = this.on_success(data, text_status, xhr);
+            def.resolve(options);
+        });
+        command.on_error = lang.hitch(this, function(xhr, text_status, error_thrown) {
+            var options = this.on_error(xhr, text_status, error_thrown);
+            def.reject(error_thrown);
+        });
+        command.execute();
+        return def;
+    },
+
+    /** @protected */
+    create_search_command: function(filter) {
+        return rpc.command({
+            entity: this.entity.name,
+            method: 'find',
+            args: [filter],
+            options: this.filter_options
+        });
+    },
+
+    /** @protected */
+    on_success: function(data, text_status, xhr) {
+
+        var adapter = builder.build('adapter', 'adapter', { context: this });
+        var options = [];
+        var entries = data.result.result;
+
+        for (var i=0; i<data.result.count; i++) {
+            var entry = entries[i];
+            var values = adapter.load(entry);
+            var value = values[0];
+            options.push(value);
+        }
+
+        return options;
+    },
+
+    /** @protected */
+    on_error: function(xhr, text_status, error_thrown) {
+    },
+
+    constructor: function(spec) {
+        this.entity = reg.entity.get(spec.entity);
+        this.param = spec.param || (this.entity ? this.entity.metadata.primary_key : null);
+        this.filter_options = spec.filter_options;
+    }
+});
+
+
+/**
+ * Uses multiple search providers and combines their result
+ * @class widget.JointCBSearchProvider
+ * @extends widget.CBSearchProvider
+ */
+exp.JointCBSearchProvider = declare([exp.CBSearchProvider], {
+
+    /**
+     * @property {widget.CBSearchProvider[]}
+     */
+    providers: [],
+
+    /**
+     * @inheritDoc
+     * @protected
+     */
+    _search: function(filter) {
+
+        var def = new Deferred();
+        var defs = [];
+        for (var i=0, l=this.providers.length;  i<l; i++) {
+            defs.push(this.providers[i].search(filter));
+        }
+        all(defs).then(function(results) {
+            var res = [];
+            for (var j=0, k=results.length; j<k; j++) {
+                res.push.apply(res, results[j]);
+            }
+            def.resolve(res);
+        } , function(errors) {
+            def.reject(errors);
+        });
+        return def;
+    },
+
+    /**
+     * Constructor
+     *
+     * Spec options:
+     *
+     * * providers - list of spec or search providers
+     */
+    constructor: function(spec) {
+        this.providers = builder.build(null, spec.providers || [], {}, {
+            $ctor: exp.CBSearchProvider
+        });
+    }
+});
+
+
+
+/**
  * Combobox widget
  *
  * Widget which allows to select a value from a predefined set or write custom
@@ -3584,13 +3769,16 @@ IPA.combobox_widget = function(spec) {
 
     var that = IPA.text_widget(spec);
 
-    that.editable = spec.editable;
+    that.editable = spec.editable === undefined ? false : spec.editable;
     that.searchable = spec.searchable;
     that.size = spec.size || 5;
     that.empty_option = spec.empty_option === undefined ? true : spec.empty_option;
     that.options = spec.options || [];
     that.z_index = spec.z_index ? spec.z_index + 9000000 : 9000000;
     that.base_css_class = that.base_css_class + ' combobox-widget';
+    that.search_provider = builder.build(null, spec.search_provider, {}, {
+        $ctor: exp.CBSearchProvider
+    });
 
     that.create = function(container) {
         that.widget_create(container);
@@ -3876,8 +4064,15 @@ IPA.combobox_widget = function(spec) {
 
     that.search = function(filter, on_success, on_error) {
 
-        that.recreate_options();
-        if (on_success) on_success.call(this);
+        if (!that.search_provider) {
+            that.set_options(that.options);
+            return;
+        }
+        if (on_success) on.once(that.search_provider, 'success', on_success);
+        if (on_error) on.once(that.search_provider, 'error', on_error);
+        when(that.search_provider.search(filter), function(options) {
+            that.set_options(options);
+        });
     };
 
     that.set_options = function(options) {
@@ -4025,8 +4220,6 @@ IPA.combobox_widget = function(spec) {
  * Specialized combobox which allows to select an entity. Widget performs
  * search - an RPC call - to get a list of entities.
  *
- * TODO: document properties and methods
- *
  * @class
  * @extends IPA.combobox_widget
  *
@@ -4040,58 +4233,13 @@ IPA.entity_select_widget = function(spec) {
 
     spec = spec || {};
     spec.searchable = spec.searchable === undefined ? true : spec.searchable;
-
+    spec.search_provider = spec.search_provider || {
+        $ctor: exp.EntityCBSearchProvider,
+        entity: spec.other_entity,
+        param: spec.other_field,
+        filter_options: spec.filter_options || {}
+    };
     var that = IPA.combobox_widget(spec);
-
-    that.other_entity = IPA.get_entity(spec.other_entity);
-    that.other_field = spec.other_field;
-
-    that.options = spec.options || [];
-    that.filter_options = spec.filter_options || {};
-
-    that.create_search_command = function(filter) {
-        return rpc.command({
-            entity: that.other_entity.name,
-            method: 'find',
-            args: [filter],
-            options: that.filter_options
-        });
-    };
-
-    that.search = function(filter, on_success, on_error) {
-
-        that.on_search_success = on_success;
-
-        var command = that.create_search_command(filter);
-        command.on_success = that.search_success;
-        command.on_error = on_error;
-
-        command.execute();
-    };
-
-    that.search_success = function(data, text_status, xhr) {
-
-        var adapter = builder.build('adapter', 'adapter', { context: that });
-
-        //get options
-        var options = [];
-
-        var entries = data.result.result;
-        for (var i=0; i<data.result.count; i++) {
-            var entry = entries[i];
-            var values = adapter.load(entry, that.other_field);
-            var value = values[0];
-
-            options.push(value);
-        }
-
-        that.set_options(options);
-
-        if (that.on_search_success) that.on_search_success.call(this, data, text_status, xhr);
-    };
-
-    that.entity_select_set_options = that.set_options;
-
     return that;
 };
 
-- 
1.9.3

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to