On 2/3/2011 8:41 AM, Adam Young wrote:
NACK. Mostly good, but not sure I agree 100%. Line level Undo we very
specific for the multi values. Undo should be for individuals, not for
the overall.

I realize that this makes the logic a little bit harder if you want to,
say, abandon your changes on phonen numbers, but keep them for Title, it
is hard to get the undo just right.

So: Multi values should have an "undo all" in addition to line level undo.

Attached is an updated patch. The line-level undo has been added.

I'd like to leave the "line-out" approach in there for removed entries
as well. A user can always repurpose a line, so there undo/redo will be
valuable at the line level. For straight delete, I think it is valuable
for the user to see the original value.

Also, it looks like the code for "create_remove_link" is still in
IPA.details_field. I'm guessing that this is dead code that should be
removed. At a minimum, it should be moved to the new widget.

Line-out removal has been added as well. Please see the new patch description. Thanks!

--
Endi S. Dewata
From 055f18eba826a46ec0d182316808b294db1fe9f6 Mon Sep 17 00:00:00 2001
From: Endi S. Dewata <edew...@redhat.com>
Date: Wed, 2 Feb 2011 16:18:35 -0600
Subject: [PATCH] Added multi-valued text widget.

A multi-valued text widget has been created to replace the old
IPA.details_field. The old code was designed to handle all data
types, and it uses one <dd> tag for each value, so the code is
still incomplete and complex. The new code was designed to handle
only multi-valued text attributes, and it uses one <dd> tag for
all values, so it's easier to maintain. There are already other
widgets that can be used to handle other data types.

The new code supports line-level undo and line-out for removal
like the old code, but there are some changes:
 - Undoing a newly added line will remove the entire line.
 - Editing the value of a removed line will cancel the removal.
 - It provides 'undo all' link to reset the entire attribute.

The old code will be cleaned up in a subsequent patch.
---
 install/ui/details.js            |   15 +-
 install/ui/ipa.css               |    4 +-
 install/ui/test/details_tests.js |   46 ++----
 install/ui/test/widget_tests.js  |   34 ++++-
 install/ui/user.js               |   10 +-
 install/ui/widget.js             |  302 +++++++++++++++++++++++++++++++++++++-
 6 files changed, 360 insertions(+), 51 deletions(-)

diff --git a/install/ui/details.js b/install/ui/details.js
index 125ef1bc85bbdd5dea488061563539db41557dda..5872e81cff4aa97e0d35cd78d21e9fec75e617a5 100644
--- a/install/ui/details.js
+++ b/install/ui/details.js
@@ -333,6 +333,12 @@ IPA.details_section = function (spec){
         return that;
     };
 
+    that.multivalued_text = function(spec) {
+        var field = IPA.multivalued_text_widget(spec);
+        that.add_field(field);
+        return that;
+    };
+
     that.create_field = function(spec) {
 
         //TODO: replace IPA.details_field with class-specific implementation
@@ -510,17 +516,14 @@ IPA.details_list_section = function (spec){
         for (var i = 0; i < fields.length; ++i) {
             var field = fields[i];
 
-            var label = field.label;
+            var label = field.label || '';
 
             // no need to get i18n label from metadata
             // because it's already done by field.init()
 
-            if (label !== '') {
-                label += ':';
-            }
-
             $('<dt/>', {
-                html: label
+                html: label+':',
+                title: label
             }).appendTo(dl);
 
             var span = $('<span/>', { 'name': field.name }).appendTo(dl);
diff --git a/install/ui/ipa.css b/install/ui/ipa.css
index 1f34b3ce4ff98d1e92b01cec91902bba04ce5dc4..c851a400e65677963b2f3972fb5bd45642529459 100644
--- a/install/ui/ipa.css
+++ b/install/ui/ipa.css
@@ -246,7 +246,7 @@ dl.entryattrs dd {
 dl.entryattrs dd.first {
     margin-left: 0;
     margin-top: 0.5em;
-        font-weight: bold;
+    font-weight: bold;
 }
 
 dl.entryattrs dd.other {
@@ -256,7 +256,7 @@ dl.entryattrs dd.other {
 
 dl.entryattrs input {
     margin-right: 0.5em;
-    margin-top: -1.2em;
+    margin-bottom: 1em;
 }
 
 dl.entryattrs input.otp {
diff --git a/install/ui/test/details_tests.js b/install/ui/test/details_tests.js
index fb33808c7faf60bbeda9090ebd20fa5a64b94113..cf4de0825cff3ef18eb6b421455ae5d650bb6f9c 100644
--- a/install/ui/test/details_tests.js
+++ b/install/ui/test/details_tests.js
@@ -21,12 +21,24 @@
 
 module('details', {
     setup: function() {
+        IPA.ajax_options.async = false;
+
+        IPA.init(
+            "data",
+            true,
+            function(data, text_status, xhr) {
+            },
+            function(xhr, text_status, error_thrown) {
+                ok(false, "ipa_init() failed: "+error_thrown);
+            }
+        );
+
         var obj_name = 'user';
         IPA.entity_factories.user=  
             function(){
                 return IPA.entity({name:obj_name});
             };
-    IPA.start_entities();
+        IPA.start_entities();
     },
     teardown: function() {
     }
@@ -35,24 +47,13 @@ module('details', {
 
 test("Testing IPA.details_section.create().", function() {
 
-    IPA.ajax_options.async = false;
-
-    IPA.init(
-        "data",
-        true,
-        function(data, text_status, xhr) {
-            ok(true, "ipa_init() succeeded.");
-        },
-        function(xhr, text_status, error_thrown) {
-            ok(false, "ipa_init() failed: "+error_thrown);
-        }
-    );
-
     var section = IPA.stanza({name:'IDIDID', label:'NAMENAMENAME'}).
         input({name:'cn'}).
-        input({name:'description'}).
-        input({name:'number'});
+        input({name:'uid'}).
+        input({name:'mail'});
 
+    section.entity_name = 'user';
+    section.init();
 
     var fields = section.fields;
     var container = $("<div/>");
@@ -105,19 +106,6 @@ test("Testing IPA.details_section.create().", function() {
 
 test("Testing details lifecycle: create, setup, load.", function(){
 
-    IPA.ajax_options.async = false;
-
-    IPA.init(
-        "data",
-        true,
-        function(data, text_status, xhr) {
-            ok(true, "ipa_init() succeeded.");
-        },
-        function(xhr, text_status, error_thrown) {
-            ok(false, "ipa_init() failed: "+error_thrown);
-        }
-    );
-
     var result = {};
 
     IPA.cmd(
diff --git a/install/ui/test/widget_tests.js b/install/ui/test/widget_tests.js
index 3d827cdb88b2324ccaae4ab01dc2b021a12a56f0..f4281e38fa173de50fe5390c0f476781a2868377 100644
--- a/install/ui/test/widget_tests.js
+++ b/install/ui/test/widget_tests.js
@@ -67,7 +67,7 @@ function base_widget_test(widget,entity_name, value){
 }
 
 
-function widget_string_test(widget, value){
+function widget_string_test(widget) {
    var value = 'test_title';
     var mock_record = {'title': value};
 
@@ -117,6 +117,31 @@ function text_tests(widget,input){
 
 }
 
+function multivalued_text_tests(widget) {
+
+    var values = ['val1', 'val2', 'val3'];
+
+    var record = {};
+    record[widget.name] = values;
+
+    widget.load(record);
+
+    same(widget.save(), values, "All values loaded");
+    same(widget.is_dirty(), false, "Field initially clean");
+
+    values = ['val1', 'val2', 'val3', 'val4'];
+    widget.add_row('val4');
+
+    same(widget.save(), values, "Value added");
+    same(widget.is_dirty(), true, "Field is dirty");
+
+    values = ['val1', 'val3', 'val4'];
+    widget.remove_row(1);
+
+    same(widget.save(), values, "Value removed");
+    same(widget.is_dirty(), true, "Field is dirty");
+}
+
 test("IPA.table_widget" ,function(){
     var widget = IPA.table_widget({undo:true,name:'users'});
 
@@ -191,6 +216,13 @@ test("Testing text widget.", function() {
 
 });
 
+test("Testing multi-valued text widget.", function() {
+    var widget = IPA.multivalued_text_widget({undo:true,name:'title'});
+    base_widget_test(widget,'user','test_value');
+    widget_string_test(widget);
+    multivalued_text_tests(widget);
+});
+
 test("Testing checkbox widget.", function() {
     var widget = IPA.checkbox_widget({name:'title'});
     base_widget_test(widget,'user','test_value');
diff --git a/install/ui/user.js b/install/ui/user.js
index 1ce95009f466edce9a529efdc0dc255f3d296a45..663aab92092916433aca1a67636107754b38d931 100644
--- a/install/ui/user.js
+++ b/install/ui/user.js
@@ -64,11 +64,11 @@ IPA.entity_factories.user = function (){
                     input({name:'homedirectory'})).
             section(
                 IPA.stanza({name: 'contact', label: IPA.messages.details.contact}).
-                    input({name:'mail'}).
-                    input({name:'telephonenumber'}).
-                    input({name:'pager'}).
-                    input({name:'mobile'}).
-                    input({name:'facsimiletelephonenumber'})).
+                    multivalued_text({name:'mail'}).
+                    multivalued_text({name:'telephonenumber'}).
+                    multivalued_text({name:'pager'}).
+                    multivalued_text({name:'mobile'}).
+                    multivalued_text({name:'facsimiletelephonenumber'})).
             section(
                 IPA.stanza({name: 'mailing', label: IPA.messages.details.mailing}).
                     input({name:'street'}).
diff --git a/install/ui/widget.js b/install/ui/widget.js
index 8f3eeb62f8dbf56d452b72890280852a77562fbd..db9d1744eb7c00979b92a8e10a3af8870e9e37bd 100644
--- a/install/ui/widget.js
+++ b/install/ui/widget.js
@@ -2,6 +2,7 @@
 /*  Authors:
  *    Endi Sukma Dewata <edew...@redhat.com>
  *    Adam Young <ayo...@redhat.com>
+ *    Pavel Zuna <pz...@redhat.com>
  *
  * Copyright (C) 2010 Red Hat
  * see file 'COPYING' for use and warranty information
@@ -34,8 +35,13 @@ IPA.widget = function(spec) {
     that.tooltip = spec.tooltip;
 
     that.disabled = spec.disabled;
+
+    // read_only is set during initialization
     that.read_only = spec.read_only;
 
+    // writable is set during load
+    that.writable = true;
+
     that._entity_name = spec.entity_name;
 
     that.width = spec.width;
@@ -80,7 +86,7 @@ IPA.widget = function(spec) {
         if ( !text || text.match(regex) ) {
             error_link.css('display', 'none');
             that.valid = true;
-        }else{
+        } else {
             error_link.css('display', 'block');
             if (that.param_info.pattern_errmsg) {
                 error_link.html(that.param_info.pattern_errmsg);
@@ -127,6 +133,23 @@ IPA.widget = function(spec) {
             that.values = value ? [value] : [];
         }
 
+        that.writable = true;
+
+        if (that.param_info.primary_key) {
+            that.writable = false;
+        }
+
+        if ('no_update' in that.param_info.flags) {
+            that.writable = false;
+        }
+
+        if (that.record.attributelevelrights) {
+            var rights = that.record.attributelevelrights[that.name];
+            if (!rights || rights.indexOf('w') < 0) {
+                that.writable = false;
+            }
+        }
+
         that.reset();
     }
 
@@ -272,21 +295,21 @@ IPA.text_widget = function(spec) {
 
         container.append(' ');
 
-        $("<span/>",{
-            name:'error_link',
-            html:"Text does not match field pattern",
-            "class":"ui-state-error ui-corner-all",
-            style:"display:none"
+        $('<span/>', {
+            name: 'error_link',
+            html: 'Text does not match field pattern',
+            'class': 'ui-state-error ui-corner-all',
+            style: 'display:none'
         }).appendTo(container);
     };
 
     that.setup = function(container) {
 
-        this.widget_setup(container);
+        that.widget_setup(container);
 
         var input = $('input[name="'+that.name+'"]', that.container);
         input.keyup(function() {
-            if(that.undo){
+            if (that.undo) {
                 that.show_undo();
             }
             var value = $(this).val();
@@ -343,6 +366,269 @@ IPA.text_widget = function(spec) {
     return that;
 };
 
+IPA.multivalued_text_widget = function(spec) {
+
+    spec = spec || {};
+
+    var that = IPA.widget(spec);
+
+    that.size = spec.size || 30;
+
+    that.get_undo = function(index) {
+        if (index === undefined) {
+            return $('span[name="undo_all"]', that.container);
+
+        } else {
+            var row = that.get_row(index);
+            return $('span[name="undo"]', row);
+        }
+    };
+
+    that.show_undo = function(index) {
+        var undo = that.get_undo(index);
+        undo.css('display', 'inline');
+    };
+
+    that.hide_undo = function(index) {
+        var undo = that.get_undo(index);
+        undo.css('display', 'none');
+    };
+
+    that.create = function(container) {
+
+        var dd = $('<dd/>', {
+            'class': 'first'
+        }).appendTo(container);
+
+        var div = $('<div/>', {
+            name: 'value'
+        }).appendTo(dd);
+
+        $('<input/>', {
+            type: 'text',
+            name: that.name,
+            disabled: that.disabled,
+            size: that.size,
+            title: that.tooltip
+        }).appendTo(div);
+
+        div.append(' ');
+
+        $('<a/>', {
+            name: 'remove',
+            href: 'jslink',
+            title: 'Remove',
+            html: 'Remove'
+        }).appendTo(div);
+
+        if (that.undo) {
+            div.append(' ');
+            that.create_undo(div);
+        }
+
+        div.append(' ');
+
+        $('<span/>', {
+            name: 'error_link',
+            html: 'Text does not match field pattern',
+            'class': 'ui-state-error ui-corner-all',
+            style: 'display:none'
+        }).appendTo(div);
+
+        $('<a/>', {
+            name: 'add',
+            href: 'jslink',
+            title: 'Add',
+            html: 'Add'
+        }).appendTo(dd);
+
+        dd.append(' ');
+
+        $('<span/>', {
+            name: 'undo_all',
+            style: 'display: none;',
+            'class': 'ui-state-highlight ui-corner-all undo',
+            html: 'undo all'
+        }).appendTo(dd);
+    };
+
+    that.setup = function(container) {
+
+        that.widget_setup(container);
+
+        that.template = $('div[name=value]', that.container);
+        that.template.detach();
+
+        var undo = that.get_undo();
+        undo.click(function() {
+            that.reset();
+        });
+
+        var add_link = $('a[name=add]', that.container);
+        add_link.click(function() {
+            that.add_row('');
+            var input = $('input[name="'+that.name+'"]:last', that.container);
+            input.focus();
+            return false;
+        });
+    };
+
+    that.save = function() {
+        var values = [];
+        if (that.read_only || !that.writable) {
+            $('label[name="'+that.name+'"]', that.container).each(function() {
+                var input = $(this);
+                var value = $.trim(input.html());
+                values.push(value);
+            });
+
+        } else {
+            $('input[name="'+that.name+'"]', that.container).each(function() {
+                var input = $(this);
+                if (input.is('.strikethrough')) return;
+
+                var value = $.trim(input.val());
+                values.push(value);
+            });
+        }
+        return values;
+    };
+
+    that.add_row = function(value) {
+
+        var add_link = $('a[name=add]', that.container);
+
+        var row = that.template.clone();
+        row.insertBefore(add_link);
+
+        var input = $('input[name="'+that.name+'"]', row);
+        var remove_link = $('a[name=remove]', row);
+        var undo_link = $('span[name=undo]', row);
+
+        if (that.read_only || !that.writable) {
+            var label = $('<label/>', {
+                'name': that.name,
+                'html': value
+            });
+            input.replaceWith(label);
+
+            remove_link.css('display', 'none');
+
+        } else {
+            input.val(value);
+
+            var index = that.row_index(row);
+            if (index >= that.values.length) {
+                // show undo/remove link for new value
+                if (that.undo) {
+                    that.show_undo(index);
+                    that.show_undo();
+                    remove_link.css('display', 'none');
+                } else {
+                    remove_link.css('display', 'inline');
+                }
+            }
+
+            input.keyup(function() {
+                var index = that.row_index(row);
+                // uncross removed value
+                input.removeClass('strikethrough');
+                if (that.undo) {
+                    that.show_undo(index);
+                    that.show_undo();
+                    if (index < that.values.length) {
+                        remove_link.css('display', 'inline');
+                    }
+                }
+                var value = $(this).val();
+                that.validate_input(value);
+            });
+
+            remove_link.click(function() {
+                var index = that.row_index(row);
+                if (index < that.values.length) {
+                    // restore old value then cross it out
+                    that.update(index);
+                    input.addClass('strikethrough');
+                    if (that.undo) {
+                        that.show_undo(index);
+                        that.show_undo();
+                    }
+                    remove_link.css('display', 'none');
+                } else {
+                    // remove new value
+                    row.remove();
+                }
+                return false;
+            });
+
+            undo_link.click(function() {
+                var index = that.row_index(row);
+                if (index < that.values.length) {
+                    // restore old value
+                    that.reset(index);
+                    input.removeClass('strikethrough');
+                    remove_link.css('display', 'inline');
+                } else {
+                    // remove new value
+                    row.remove();
+                }
+            });
+        }
+    };
+
+    that.remove_row = function(index) {
+        that.get_row(index).remove();
+    };
+
+    that.get_row = function(index) {
+        return $('div[name=value]:eq('+index+')', that.container);
+    };
+
+    that.get_rows = function() {
+        return $('div[name=value]', that.container);
+    };
+
+    that.row_index = function(row) {
+        return that.get_rows().index(row);
+    };
+
+    that.reset = function(index) {
+        that.hide_undo(index);
+        that.update(index);
+    };
+
+    that.update = function(index) {
+
+        var value;
+
+        if (index === undefined) {
+            $('div[name=value]', that.container).remove();
+
+            for (var i=0; i<that.values.length; i++) {
+                value = that.values[i];
+                that.add_row(value);
+            }
+
+            var add_link = $('a[name=add]', that.container);
+
+            if (that.read_only || !that.writable) {
+                add_link.css('display', 'none');
+            } else {
+                add_link.css('display', 'inline');
+            }
+
+        } else {
+            value = that.values[index];
+            var row = that.get_row(index);
+            var input = $('input[name="'+that.name+'"]', row);
+            input.val(value);
+        }
+    };
+
+    return that;
+};
+
 IPA.checkbox_widget = function (spec) {
 
     spec = spec || {};
-- 
1.6.6.1

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

Reply via email to