diff options
author | Petr Vobornik <pvoborni@redhat.com> | 2013-03-22 17:53:04 +0100 |
---|---|---|
committer | Petr Vobornik <pvoborni@redhat.com> | 2013-03-29 17:12:20 +0100 |
commit | 04325fbb4c64ee4aef6d8c9adf0ff95b8b653101 (patch) | |
tree | ea424198852f81e1b08695f93a65b2ec343b5052 /install | |
parent | 5f26d2c6dbe878518963b5d8f9159ed3fcc71d58 (diff) | |
download | freeipa.git-04325fbb4c64ee4aef6d8c9adf0ff95b8b653101.tar.gz freeipa.git-04325fbb4c64ee4aef6d8c9adf0ff95b8b653101.tar.xz freeipa.git-04325fbb4c64ee4aef6d8c9adf0ff95b8b653101.zip |
Nestable checkbox/radio widget
New component: option_widget_base. It's not a regular widget but it share some of its characteristics. It should extend regular widget or it can be nested in itself alone.
checkbox_widget, checkboxes_widget, radio_widget were modified to use it.
Built as a prerequisite for:
https://fedorahosted.org/freeipa/ticket/3404
Diffstat (limited to 'install')
-rw-r--r-- | install/ui/ipa.css | 15 | ||||
-rw-r--r-- | install/ui/src/freeipa/aci.js | 15 | ||||
-rw-r--r-- | install/ui/src/freeipa/rule.js | 3 | ||||
-rw-r--r-- | install/ui/src/freeipa/widget.js | 500 |
4 files changed, 390 insertions, 143 deletions
diff --git a/install/ui/ipa.css b/install/ui/ipa.css index 71cad420..3e443d54 100644 --- a/install/ui/ipa.css +++ b/install/ui/ipa.css @@ -1294,6 +1294,21 @@ table.scrollable tbody { width: 400px; } +.option_widget { + list-style-type: none; + margin: 0; + padding: 0; +} + +.option_widget.nested { + padding-left: 40px; +} + +.option_widget.inline, +.option_widget.inline > li { + display: inline; +} + .combobox-widget-input { display: inline-block; position: relative; diff --git a/install/ui/src/freeipa/aci.js b/install/ui/src/freeipa/aci.js index 383848ec..b6825d13 100644 --- a/install/ui/src/freeipa/aci.js +++ b/install/ui/src/freeipa/aci.js @@ -477,7 +477,7 @@ IPA.attributes_widget = function(spec) { 'class': 'aci-attribute-table-container' }).appendTo(container); - that.table = $('<table/>', { + that.$node = that.table = $('<table/>', { id:id, 'class':'search-table aci-attribute-table scrollable' }). @@ -520,9 +520,12 @@ IPA.attributes_widget = function(spec) { var tr = $('<tr/>').appendTo(tbody); var td = $('<td/>').appendTo(tr); + var name = that.get_input_name(); + var id = that.option_next_id + name; td.append($('<input/>',{ + id: id, type: 'checkbox', - name: that.name, + name: name, value: value, 'class': 'aci-attribute', change: function() { @@ -531,8 +534,10 @@ IPA.attributes_widget = function(spec) { })); td = $('<td/>').appendTo(tr); td.append($('<label/>',{ - text: value + text: value, + 'for': id })); + that.new_option_id(); } }; @@ -553,7 +558,7 @@ IPA.attributes_widget = function(spec) { that.populate(that.object_type); that.append(); - that.checkboxes_update(values); + that.owb_update(values); }; that.populate = function(object_type) { @@ -567,6 +572,7 @@ IPA.attributes_widget = function(spec) { var aciattrs = metadata.aciattrs; + that.options = that.prepare_options(aciattrs); that.create_options(aciattrs); }; @@ -585,6 +591,7 @@ IPA.attributes_widget = function(spec) { } if (unmatched.length > 0 && !that.skip_unmatched) { + that.options.push.apply(that.options, that.prepare_options(unmatched)); that.create_options(unmatched); } }; diff --git a/install/ui/src/freeipa/rule.js b/install/ui/src/freeipa/rule.js index 888fd450..436e934c 100644 --- a/install/ui/src/freeipa/rule.js +++ b/install/ui/src/freeipa/rule.js @@ -43,7 +43,8 @@ IPA.rule_details_widget = function(spec) { that.enable_radio.value_changed.attach(that.on_enable_radio_changed); }; - that.on_enable_radio_changed = function(value) { + that.on_enable_radio_changed = function() { + var value = that.enable_radio.save(); if(value.length > 0) { var enabled = ('' === value[0]); for (var i=0; i<that.tables.length; i++) { diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js index 5ce767e7..44b8611a 100644 --- a/install/ui/src/freeipa/widget.js +++ b/install/ui/src/freeipa/widget.js @@ -21,7 +21,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -define(['./ipa', './jquery'], function(IPA, $) { +define(['dojo/_base/array', './ipa', './jquery'], function(array, IPA, $) { IPA.checkbox_column_width = 22; IPA.required_indicator = '*'; @@ -616,147 +616,395 @@ IPA.multivalued_widget = function(spec) { return that; }; -IPA.checkbox_widget = function (spec) { +/** + * Widget base for checkboxes and radios. Doesn't handle dirty state's but + * its supposed to be nestable. + * + * Nesting rules: + * 1. parent should be checked when one of its child is checked + * Consequences: + * * childs get unchecked when parent gets unchecked + * * parent will be checked when child is checked even when input + * values don't contain parent's value. + * 2. parent can be configured not to include it's value when children are + * checked + * 3. each subtree containing a checked input has to return at least one value + * on save() + * 4. each option has to have unique value + * + * Has subset of widget interface - overrides the values in widget + * * save(): get values + * * update(values): set values + * * value_changed: event when change happens + * * create: creates HTML + */ +IPA.option_widget_base = function(spec, that) { spec = spec || {}; - var that = IPA.input_widget(spec); + // when that is specified, this constructor behaves like a mixin + that = that || {}; - // default value - that.checked = spec.checked || false; + // classic properties + that.name = spec.name; + that.label = spec.label; + that.tooltip = spec.tooltip; + that.value_changed = IPA.observer(); + that.default_value = spec.default_value || null; - that.create = function(container) { + /** + * Jquery reference to current node + */ + that.$node = null; - that.widget_create(container); + /** + * Type of rendered inputs: ['checkbox', 'radio'] + */ + that.input_type = spec.input_type || 'checkbox'; - container.addClass('checkbox-widget'); + /** + * CSS class for container + */ + that.css_class = spec.css_class || ''; - that.input = $('<input/>', { - type: 'checkbox', - name: that.name, - checked: that.checked, - title: that.tooltip, - change: function() { - that.value_changed.notify([that.save()], that); - } - }).appendTo(container); + /** + * If it's nested widget + */ + that.nested = !!spec.nested; - if (that.undo) { - that.create_undo(container); + /** + * How items should be rendered. + * + * values: ['inline', 'vertical'] + */ + that.layout = spec.layout || 'vertical'; + + // private properties + that._child_widgets = []; + that._input_name = null; + that._selector = null; + that._option_next_id = 0; + + + /** + * Normalizes options spec + */ + that.prepare_options = function(options) { + + var ret = []; + + if (!options) return options; + + for (var i=0; i<options.length; i++) { + ret.push(that.prepare_option(options[i])); } - that.create_error_link(container); + return ret; }; - that.save = function() { - var value = that.input.is(':checked'); - return [value]; + /** + * Option has following interface: { + * label: String + * value: String + * nested: Boolean + * widget: Widget + * combine_values: Boolean, default true. Whether to include this value + * if some of its children is specified + * } + */ + that.prepare_option = function(spec) { + + var option = spec; + + if (!option) throw { + error: 'Invalid option specified', + option: option + }; + + if (typeof option === 'string') { + option = { + label: option, + value: option + }; + } else { + if (option.type || option.factory) { + var factory = option.factory || IPA.widget_factories[option.type]; + if (typeof factory !== 'function') throw { + error: 'Invalid factory', + factory: factory + }; + option.nested = true; + option.widget = factory(option); + option.widget.value_changed.attach(that.on_input_change); + + that._child_widgets.push(option.widget); + } + } + + option.combine_values = option.combine_values === undefined ? true : + !!option.combine_values; + + return option; }; - that.update = function(values) { - var value; + that.add_option = function(option, suppress_update) { + that.options.push(that.prepare_option(option)); + if (!suppress_update) that.update_dom(); + }; + + that.update_dom = function() { - if (values && values.length) { - value = values[0]; + if (that.$node) { + var values = that.save(); + var container = that.$node.parent(); + that.create(container); + that.update(values); } + }; - if (typeof value !== 'boolean') { - // use default value - value = that.checked; + that.create_options = function(container) { + for (var i=0; i<that.options.length; i++) { + var option_container = that.create_option_container(); + var option = that.options[i]; + that.create_option(option, option_container); + option_container.appendTo(container); } + }; - that.input.prop('checked', value); + that.create_option_container = function() { + return $('<li/>'); }; - that.clear = function() { - that.input.prop('checked', false); + that._create_option = function(option, container) { + var input_name = that.get_input_name(); + var id = that._option_next_id + input_name; + + $('<input/>', { + id: id, + type: that.input_type, + name: input_name, + value: option.value, + title: option.tooltip || that.tooltip, + change: that.on_input_change + }).appendTo(container); + + $('<label/>', { + text: option.label, + title: option.tooltip || that.tooltip, + 'for': id + }).appendTo(container); + + that.new_option_id(); }; - that.checkbox_save = that.save; + that.create_option = function(option, container) { - return that; -}; + that._create_option(option, container); -IPA.checkboxes_widget = function (spec) { + if (option.widget) { + option.widget.create(container); + } + }; - spec = spec || {}; + that.new_option_id = function() { + that._option_next_id++; + }; - var that = IPA.input_widget(spec); + that.get_input_name = function() { - that.options = spec.options || []; - that.direction = spec.direction || 'vertical'; + if (!that._input_name) { + var name = that.name; + if (that.input_type === 'radio') { + name = IPA.html_util.get_next_id(name); + } + that._input_name = name; + that._selector = 'input[name="'+name+'"]'; + } + return that._input_name; + }; that.create = function(container) { + that.destroy(); + that.create_options(that.$node); + var css_class = [that.css_class, 'option_widget', that.layout, + that.nested ? 'nested': ''].join(' '); - that.widget_create(container); + that.$node = $('<ul/>', { 'class': css_class }); + that.create_options(that.$node); - container.addClass('checkboxes-widget'); + if (container) that.$node.appendTo(container); + }; + + that.destroy = function() { + if (that.$node) { + that.$node.empty(); + that.$node.remove(); + } - var vertical = that.direction === 'vertical'; + for (var i=0; i< that._child_widgets.length; i++) { + that._child_widgets[i].destroy(); + } + }; + that.get_option = function(value) { for (var i=0; i<that.options.length; i++) { var option = that.options[i]; - $('<input/>', { - type: 'checkbox', - name: that.name, - value: option.value, - title: that.tooltip - }).appendTo(container); + if (option.value === value) return option; + } + return null; + }; - $('<label/>', { - text: option.label, - title: that.tooltip - }).appendTo(container); + /** + * Get values for specific option and its children. + * + * If option is not specified, gets values of all options and children. + */ + that.get_values = function(option) { - if (vertical) { - $('<br/>').appendTo(container); + var values = []; + if (option) { + values.push(option.value); + if (option.widget) { + values.push.apply(values, option.widget.get_values()); + } + } else { + for (var i=0; i<that.options.length; i++) { + var vals = that.get_values(that.options[i]); + values.push.apply(values, vals); } } - if (that.undo) { - that.create_undo(container); - } + return values; + }; - var input = $('input[name="'+that.name+'"]', that.container); - input.change(function() { - that.value_changed.notify([that.save()], that); - }); + that.on_input_change = function(e) { - that.create_error_link(container); + // uncheck child widgets on uncheck of parent option + if (that._child_widgets.length > 0) { + + var parents_selected = []; + + $(that._selector+':checked', that.container).each(function() { + var value = $(this).val(); + var option = that.get_option(value); + if (option && option.nested) { + parents_selected.push(value); + } + }); + + for (var i=0; i<that.options.length; i++) { + + var option = that.options[i]; + + if (option.nested) { + var selected = parents_selected.indexOf(option.value) > -1; + option.widget.set_enabled(selected); + } + } + } + that.value_changed.notify([], that); }; that.save = function() { + var values = []; - $('input[name="'+that.name+'"]:checked', that.container).each(function() { - values.push($(this).val()); - }); + if (that.$node) { + + $(that._selector+':checked', that.container).each(function() { + var value = $(this).val(); + var child_values = []; + var option = that.get_option(value); + + if (option.widget) { + child_values = option.widget.save(); + values.push.apply(values, child_values); + } + + // don't use value if cannot be combined with children's value + if (!(child_values.length > 0 && !option.combine_values)) { + values.push(value); + } + }); + } return values; }; that.update = function(values) { - var inputs = $('input[name="'+that.name+'"]', that.container); - inputs.prop('checked', false); - for (var j=0; values && j<values.length; j++) { - var value = values[j]; - var input = $('input[name="'+that.name+'"][value="'+value+'"]', that.container); - if (!input.length) continue; - input.prop('checked', true); + var check = function(selector, uncheck) { + $(selector, that.$node).prop('checked', !uncheck); + }; + + if (that.$node) { + + // uncheck all inputs + check(that._selector, true /*uncheck*/); + + if (values && values.length > 0) { + + // check the option when option or some of its child should be + // checked + for (var i=0; i<that.options.length; i++) { + var option = that.options[i]; + var opt_vals = that.get_values(option); + var has_opt = array.some(values, function(val) { + return array.indexOf(opt_vals, val) > -1; + }); + + if (has_opt) { + check(that._selector+'[value="'+ option.value +'"]'); + } + if (option.widget) { + option.widget.set_enabled(has_opt); + } + } + } else { + // select default if none specified + if (that.default_value !== null) { + check(that._selector+'[value="'+that.default_value+'"]'); + } else { + // otherwise select empty + check(that._selector+'[value=""]'); + } + } + + for (var j=0; j<that._child_widgets.length; j++) { + that._child_widgets[j].update(values); + } } }; - that.clear = function() { - $('input[name="'+that.name+'"]').prop('checked', false); + that.set_enabled = function(enabled) { + + $(that._selector, that.container).prop('disabled', !enabled); + if (!enabled) that.clear(); + for (var i=0; i<that._child_widgets.length;i++){ + that._child_widgets[i].set_enabled(enabled); + } }; - that.add_option = function(option) { - that.options.push(option); + + that.clear = function() { + + $(that._selector, that.$node).prop('checked', false); + + if (that.default_value) { + $(that._selector+'[value="'+that.default_value+'"]', that.$node). + prop('checked', true); + } + + for (var i=0; i<that._child_widgets.length; i++) { + that._child_widgets[i].clear(); + } }; - // methods that should be invoked by subclasses - that.checkboxes_update = that.update; + that.options = that.prepare_options(spec.options || []); + + that.owb_create = that.create; + that.owb_save = that.save; + that.owb_update = that.update; return that; }; @@ -764,89 +1012,65 @@ IPA.checkboxes_widget = function (spec) { IPA.radio_widget = function(spec) { spec = spec || {}; + spec.input_type = spec.input_type || 'radio'; + spec.layout = spec.layout || 'inline'; var that = IPA.input_widget(spec); - - that.default_value = spec.default_value; - that.options = spec.options; + IPA.option_widget_base(spec, that); that.create = function(container) { - that.widget_create(container); - + that.owb_create(container); container.addClass('radio-widget'); - var name = IPA.html_util.get_next_id(that.name+'-'); - that.selector = 'input[name="'+name+'"]'; - - for (var i=0; i<that.options.length; i++) { - var option = that.options[i]; - - var id = name+'-'+i; - - $('<input/>', { - id: id, - type: 'radio', - name: name, - value: option.value - }).appendTo(container); - - $('<label/>', { - text: option.label, - 'for': id - }).appendTo(container); - } - if (that.undo) { that.create_undo(container); } - var input = $(that.selector, that.container); - input.change(function() { - that.value_changed.notify([that.save()], that); - }); - that.create_error_link(container); }; + return that; +}; - that.save = function() { - var input = $(that.selector+':checked', that.container); - if (!input.length) return []; - return [input.val()]; - }; +IPA.checkbox_widget = function (spec) { - that.update = function(values) { + var checked = 'checked'; - $(that.selector, that.container).each(function() { - var input = this; - input.checked = false; - }); + spec = spec || {}; + spec.input_type = spec.input_type || 'checkbox'; - var value = values && values.length ? values[0] : ''; - var input = $(that.selector+'[value="'+value+'"]', that.container); - if (input.length) { - input.prop('checked', true); - } else if (that.default_value) { - input = $(that.selector+'[value="'+that.default_value+'"]', that.container); - input.prop('checked', true); - } + if (!spec.options) { + spec.options = [ { value: checked, label: '' } ]; + } + + if (spec.checked) spec.default_value = spec.checked; + + var that = IPA.radio_widget(spec); - that.value_changed.notify([that.save()], that); + that.save = function() { + var values = that.owb_save(); + return [values.length > 0]; }; - that.clear = function() { - $(that.selector, that.container).prop('checked', false); + that.update = function(values) { + var value = values ? values[0] : ''; - if (that.default_value) { - var input = $(that.selector+'[value="'+that.default_value+'"]', that.container); - input.prop('checked', true); + if (typeof value !== 'boolean') { + value = that.default_value || ''; + } else { + value = value ? checked : ''; } + that.owb_update([value]); }; - // methods that should be invoked by subclasses - that.radio_create = that.create; - that.radio_save = that.save; + return that; +}; +IPA.checkboxes_widget = function (spec) { + spec = spec || {}; + spec.input_type = spec.input_type || 'checkbox'; + spec.layout = spec.layout || 'vertical'; + var that = IPA.radio_widget(spec); return that; }; |